Support App List match any user for admin
If set, an app will displayed in owner's App List even it not installed for the owner (not installed on every profile of the owner). Bug: 277299765 Test: Manually with All Apps when multiple users is on Test: Unit test Change-Id: I81119e20ee46b4dc3bfebe8d10c0978f439de2eb
This commit is contained in:
@@ -20,9 +20,12 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.ApplicationInfoFlags
|
||||
import android.content.pm.ResolveInfo
|
||||
import com.android.internal.R
|
||||
import com.android.settingslib.spaprivileged.framework.common.userManager
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -33,7 +36,11 @@ import kotlinx.coroutines.runBlocking
|
||||
*/
|
||||
internal interface AppListRepository {
|
||||
/** Loads the list of [ApplicationInfo]. */
|
||||
suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo>
|
||||
suspend fun loadApps(
|
||||
userId: Int,
|
||||
showInstantApps: Boolean = false,
|
||||
matchAnyUserForAdmin: Boolean = false,
|
||||
): List<ApplicationInfo>
|
||||
|
||||
/** Gets the flow of predicate that could used to filter system app. */
|
||||
fun showSystemPredicate(
|
||||
@@ -61,10 +68,12 @@ object AppListRepositoryUtil {
|
||||
|
||||
internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
|
||||
private val packageManager = context.packageManager
|
||||
private val userManager = context.userManager
|
||||
|
||||
override suspend fun loadApps(
|
||||
userId: Int,
|
||||
showInstantApps: Boolean,
|
||||
matchAnyUserForAdmin: Boolean,
|
||||
): List<ApplicationInfo> = coroutineScope {
|
||||
val hiddenSystemModulesDeferred = async {
|
||||
packageManager.getInstalledModules(0)
|
||||
@@ -75,12 +84,8 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo
|
||||
val hideWhenDisabledPackagesDeferred = async {
|
||||
context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
|
||||
}
|
||||
val flags = PackageManager.ApplicationInfoFlags.of(
|
||||
(PackageManager.MATCH_DISABLED_COMPONENTS or
|
||||
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
|
||||
)
|
||||
val installedApplicationsAsUser =
|
||||
packageManager.getInstalledApplicationsAsUser(flags, userId)
|
||||
getInstalledApplications(userId, matchAnyUserForAdmin)
|
||||
|
||||
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
|
||||
val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
|
||||
@@ -89,6 +94,46 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getInstalledApplications(
|
||||
userId: Int,
|
||||
matchAnyUserForAdmin: Boolean,
|
||||
): List<ApplicationInfo> {
|
||||
val regularFlags = ApplicationInfoFlags.of(
|
||||
(PackageManager.MATCH_DISABLED_COMPONENTS or
|
||||
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
|
||||
)
|
||||
return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
|
||||
packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
|
||||
} else {
|
||||
coroutineScope {
|
||||
val deferredPackageNamesInChildProfiles =
|
||||
userManager.getProfileIdsWithDisabled(userId)
|
||||
.filter { it != userId }
|
||||
.map {
|
||||
async {
|
||||
packageManager.getInstalledApplicationsAsUser(regularFlags, it)
|
||||
.map { it.packageName }
|
||||
}
|
||||
}
|
||||
val adminFlags = ApplicationInfoFlags.of(
|
||||
PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value
|
||||
)
|
||||
val allInstalledApplications =
|
||||
packageManager.getInstalledApplicationsAsUser(adminFlags, userId)
|
||||
val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles
|
||||
.awaitAll()
|
||||
.flatten()
|
||||
.toSet()
|
||||
// If an app is for a child profile and not installed on the owner, not display as
|
||||
// 'not installed for this user' in the owner. This will prevent duplicates of work
|
||||
// only apps showing up in the personal profile.
|
||||
allInstalledApplications.filter {
|
||||
it.installed || it.packageName !in packageNamesInChildProfiles
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun showSystemPredicate(
|
||||
userIdFlow: Flow<Int>,
|
||||
showSystemFlow: Flow<Boolean>,
|
||||
|
||||
@@ -80,12 +80,15 @@ internal open class AppListViewModelImpl<T : AppRecord>(
|
||||
private val scope = viewModelScope + Dispatchers.IO
|
||||
|
||||
private val userSubGraphsFlow = appListConfig.flow.map { config ->
|
||||
config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) }
|
||||
config.userIds.map { userId ->
|
||||
UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin)
|
||||
}
|
||||
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
private inner class UserSubGraph(
|
||||
private val userId: Int,
|
||||
private val showInstantApps: Boolean,
|
||||
private val matchAnyUserForAdmin: Boolean,
|
||||
) {
|
||||
private val userIdFlow = flowOf(userId)
|
||||
|
||||
@@ -110,7 +113,8 @@ internal open class AppListViewModelImpl<T : AppRecord>(
|
||||
|
||||
fun reloadApps() {
|
||||
scope.launch {
|
||||
appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps)
|
||||
appsStateFlow.value =
|
||||
appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ private const val CONTENT_TYPE_HEADER = "header"
|
||||
data class AppListConfig(
|
||||
val userIds: List<Int>,
|
||||
val showInstantApps: Boolean,
|
||||
val matchAnyUserForAdmin: Boolean,
|
||||
)
|
||||
|
||||
data class AppListState(
|
||||
|
||||
@@ -38,6 +38,7 @@ fun <T : AppRecord> AppListPage(
|
||||
title: String,
|
||||
listModel: AppListModel<T>,
|
||||
showInstantApps: Boolean = false,
|
||||
matchAnyUserForAdmin: Boolean = false,
|
||||
primaryUserOnly: Boolean = false,
|
||||
noItemMessage: String? = null,
|
||||
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
|
||||
@@ -59,6 +60,7 @@ fun <T : AppRecord> AppListPage(
|
||||
config = AppListConfig(
|
||||
userIds = userGroup.userInfos.map { it.id },
|
||||
showInstantApps = showInstantApps,
|
||||
matchAnyUserForAdmin = matchAnyUserForAdmin,
|
||||
),
|
||||
listModel = listModel,
|
||||
state = AppListState(
|
||||
|
||||
@@ -23,9 +23,13 @@ import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.ApplicationInfoFlags
|
||||
import android.content.pm.PackageManager.ResolveInfoFlags
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.content.pm.UserInfo
|
||||
import android.content.res.Resources
|
||||
import android.os.UserManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.internal.R
|
||||
import com.android.settingslib.spaprivileged.framework.common.userManager
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -35,10 +39,13 @@ import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.eq
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Spy
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.junit.MockitoRule
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
@@ -49,8 +56,8 @@ class AppListRepositoryTest {
|
||||
@get:Rule
|
||||
val mockito: MockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@Mock
|
||||
private lateinit var context: Context
|
||||
@Spy
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@Mock
|
||||
private lateinit var resources: Resources
|
||||
@@ -58,6 +65,9 @@ class AppListRepositoryTest {
|
||||
@Mock
|
||||
private lateinit var packageManager: PackageManager
|
||||
|
||||
@Mock
|
||||
private lateinit var userManager: UserManager
|
||||
|
||||
private lateinit var repository: AppListRepository
|
||||
|
||||
@Before
|
||||
@@ -66,36 +76,116 @@ class AppListRepositoryTest {
|
||||
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
|
||||
.thenReturn(emptyArray())
|
||||
whenever(context.packageManager).thenReturn(packageManager)
|
||||
whenever(context.userManager).thenReturn(userManager)
|
||||
whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
|
||||
whenever(
|
||||
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
|
||||
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt())
|
||||
).thenReturn(emptyList())
|
||||
whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply {
|
||||
flags = UserInfo.FLAG_ADMIN
|
||||
})
|
||||
whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID))
|
||||
.thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID))
|
||||
|
||||
repository = AppListRepositoryImpl(context)
|
||||
}
|
||||
|
||||
private fun mockInstalledApplications(apps: List<ApplicationInfo>) {
|
||||
private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
|
||||
whenever(
|
||||
packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
|
||||
packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId))
|
||||
).thenReturn(apps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApps_notShowInstantApps() = runTest {
|
||||
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
|
||||
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
|
||||
|
||||
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
|
||||
val appList = repository.loadApps(
|
||||
userId = ADMIN_USER_ID,
|
||||
showInstantApps = false,
|
||||
)
|
||||
|
||||
assertThat(appListFlow).containsExactly(NORMAL_APP)
|
||||
assertThat(appList).containsExactly(NORMAL_APP)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApps_showInstantApps() = runTest {
|
||||
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
|
||||
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
|
||||
|
||||
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true)
|
||||
val appList = repository.loadApps(
|
||||
userId = ADMIN_USER_ID,
|
||||
showInstantApps = true,
|
||||
)
|
||||
|
||||
assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
|
||||
assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest {
|
||||
mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
|
||||
|
||||
val appList = repository.loadApps(
|
||||
userId = ADMIN_USER_ID,
|
||||
matchAnyUserForAdmin = false,
|
||||
)
|
||||
|
||||
assertThat(appList).containsExactly(NORMAL_APP)
|
||||
val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
|
||||
verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
|
||||
assertThat(flags.value.value).isEqualTo(
|
||||
PackageManager.MATCH_DISABLED_COMPONENTS or
|
||||
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest {
|
||||
mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
|
||||
|
||||
val appList = repository.loadApps(
|
||||
userId = ADMIN_USER_ID,
|
||||
matchAnyUserForAdmin = true,
|
||||
)
|
||||
|
||||
assertThat(appList).containsExactly(NORMAL_APP)
|
||||
val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
|
||||
verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
|
||||
assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest {
|
||||
val managedProfileOnlyPackageName = "installed.on.managed.profile.only"
|
||||
mockInstalledApplications(listOf(ApplicationInfo().apply {
|
||||
packageName = managedProfileOnlyPackageName
|
||||
}), ADMIN_USER_ID)
|
||||
mockInstalledApplications(listOf(ApplicationInfo().apply {
|
||||
packageName = managedProfileOnlyPackageName
|
||||
flags = ApplicationInfo.FLAG_INSTALLED
|
||||
}), MANAGED_PROFILE_USER_ID)
|
||||
|
||||
val appList = repository.loadApps(
|
||||
userId = ADMIN_USER_ID,
|
||||
matchAnyUserForAdmin = true,
|
||||
)
|
||||
|
||||
assertThat(appList).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest {
|
||||
val secondaryUserOnlyApp = ApplicationInfo().apply {
|
||||
packageName = "installed.on.secondary.user.only"
|
||||
}
|
||||
mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID)
|
||||
mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID)
|
||||
|
||||
val appList = repository.loadApps(
|
||||
userId = ADMIN_USER_ID,
|
||||
matchAnyUserForAdmin = true,
|
||||
)
|
||||
|
||||
assertThat(appList).containsExactly(secondaryUserOnlyApp)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,11 +196,11 @@ class AppListRepositoryTest {
|
||||
}
|
||||
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
|
||||
.thenReturn(arrayOf(app.packageName))
|
||||
mockInstalledApplications(listOf(app))
|
||||
mockInstalledApplications(listOf(app), ADMIN_USER_ID)
|
||||
|
||||
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
|
||||
val appList = repository.loadApps(userId = ADMIN_USER_ID)
|
||||
|
||||
assertThat(appListFlow).isEmpty()
|
||||
assertThat(appList).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,11 +212,11 @@ class AppListRepositoryTest {
|
||||
}
|
||||
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
|
||||
.thenReturn(arrayOf(app.packageName))
|
||||
mockInstalledApplications(listOf(app))
|
||||
mockInstalledApplications(listOf(app), ADMIN_USER_ID)
|
||||
|
||||
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
|
||||
val appList = repository.loadApps(userId = ADMIN_USER_ID)
|
||||
|
||||
assertThat(appListFlow).isEmpty()
|
||||
assertThat(appList).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -137,11 +227,11 @@ class AppListRepositoryTest {
|
||||
}
|
||||
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
|
||||
.thenReturn(arrayOf(app.packageName))
|
||||
mockInstalledApplications(listOf(app))
|
||||
mockInstalledApplications(listOf(app), ADMIN_USER_ID)
|
||||
|
||||
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
|
||||
val appList = repository.loadApps(userId = ADMIN_USER_ID)
|
||||
|
||||
assertThat(appListFlow).containsExactly(app)
|
||||
assertThat(appList).containsExactly(app)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -151,11 +241,11 @@ class AppListRepositoryTest {
|
||||
enabled = false
|
||||
enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
|
||||
}
|
||||
mockInstalledApplications(listOf(app))
|
||||
mockInstalledApplications(listOf(app), ADMIN_USER_ID)
|
||||
|
||||
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
|
||||
val appList = repository.loadApps(userId = ADMIN_USER_ID)
|
||||
|
||||
assertThat(appListFlow).containsExactly(app)
|
||||
assertThat(appList).containsExactly(app)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -164,11 +254,11 @@ class AppListRepositoryTest {
|
||||
packageName = "disabled"
|
||||
enabled = false
|
||||
}
|
||||
mockInstalledApplications(listOf(app))
|
||||
mockInstalledApplications(listOf(app), ADMIN_USER_ID)
|
||||
|
||||
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
|
||||
val appList = repository.loadApps(userId = ADMIN_USER_ID)
|
||||
|
||||
assertThat(appListFlow).isEmpty()
|
||||
assertThat(appList).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -219,7 +309,11 @@ class AppListRepositoryTest {
|
||||
val app = IN_LAUNCHER_APP
|
||||
|
||||
whenever(
|
||||
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
|
||||
packageManager.queryIntentActivitiesAsUser(
|
||||
any(),
|
||||
any<ResolveInfoFlags>(),
|
||||
eq(ADMIN_USER_ID)
|
||||
)
|
||||
).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
|
||||
|
||||
val showSystemPredicate = getShowSystemPredicate(showSystem = false)
|
||||
@@ -229,12 +323,16 @@ class AppListRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun getSystemPackageNames_returnExpectedValues() = runTest {
|
||||
mockInstalledApplications(listOf(
|
||||
NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP))
|
||||
mockInstalledApplications(
|
||||
apps = listOf(
|
||||
NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
|
||||
),
|
||||
userId = ADMIN_USER_ID,
|
||||
)
|
||||
|
||||
val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
|
||||
context = context,
|
||||
userId = USER_ID,
|
||||
userId = ADMIN_USER_ID,
|
||||
showInstantApps = false,
|
||||
)
|
||||
|
||||
@@ -243,12 +341,13 @@ class AppListRepositoryTest {
|
||||
|
||||
private suspend fun getShowSystemPredicate(showSystem: Boolean) =
|
||||
repository.showSystemPredicate(
|
||||
userIdFlow = flowOf(USER_ID),
|
||||
userIdFlow = flowOf(ADMIN_USER_ID),
|
||||
showSystemFlow = flowOf(showSystem),
|
||||
).first()
|
||||
|
||||
private companion object {
|
||||
const val USER_ID = 0
|
||||
const val ADMIN_USER_ID = 0
|
||||
const val MANAGED_PROFILE_USER_ID = 11
|
||||
|
||||
val NORMAL_APP = ApplicationInfo().apply {
|
||||
packageName = "normal"
|
||||
|
||||
@@ -85,7 +85,11 @@ class AppListViewModelTest {
|
||||
}
|
||||
|
||||
private object FakeAppListRepository : AppListRepository {
|
||||
override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP)
|
||||
override suspend fun loadApps(
|
||||
userId: Int,
|
||||
showInstantApps: Boolean,
|
||||
matchAnyUserForAdmin: Boolean,
|
||||
) = listOf(APP)
|
||||
|
||||
override fun showSystemPredicate(
|
||||
userIdFlow: Flow<Int>,
|
||||
@@ -112,7 +116,11 @@ class AppListViewModelTest {
|
||||
const val USER_ID = 0
|
||||
const val PACKAGE_NAME = "package.name"
|
||||
const val LABEL = "Label"
|
||||
val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false)
|
||||
val CONFIG = AppListConfig(
|
||||
userIds = listOf(USER_ID),
|
||||
showInstantApps = false,
|
||||
matchAnyUserForAdmin = false,
|
||||
)
|
||||
val APP = ApplicationInfo().apply {
|
||||
packageName = PACKAGE_NAME
|
||||
}
|
||||
|
||||
@@ -114,7 +114,11 @@ class AppListTest {
|
||||
) {
|
||||
composeTestRule.setContent {
|
||||
AppListInput(
|
||||
config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false),
|
||||
config = AppListConfig(
|
||||
userIds = listOf(USER_ID),
|
||||
showInstantApps = false,
|
||||
matchAnyUserForAdmin = false,
|
||||
),
|
||||
listModel = TestAppListModel(enableGrouping = enableGrouping),
|
||||
state = AppListState(
|
||||
showSystem = false.toState(),
|
||||
|
||||
Reference in New Issue
Block a user