Display cloned app under person tab in App List

This is for the new SPA App List.

Before this change, cloned app is displayed in separated tab, after this
change, cloned app will displayed under person tab in App List.

Fix: 266040895
Test: Manually with Settings
Test: Unit test
Change-Id: I0dd448c3a33815b9b63348381d97b2520e05ab27
This commit is contained in:
Chaohui Wang
2023-01-17 15:18:57 +08:00
parent f104ba946c
commit d6b7f54961
11 changed files with 220 additions and 174 deletions

View File

@@ -33,7 +33,6 @@ import com.android.settingslib.spa.framework.compose.LifecycleEffect
fun DisposableBroadcastReceiverAsUser(
intentFilter: IntentFilter,
userHandle: UserHandle,
onStart: () -> Unit = {},
onReceive: (Intent) -> Unit,
) {
val context = LocalContext.current
@@ -49,7 +48,6 @@ fun DisposableBroadcastReceiverAsUser(
context.registerReceiverAsUser(
broadcastReceiver, userHandle, intentFilter, null, null
)
onStart()
},
onStop = {
context.unregisterReceiver(broadcastReceiver)

View File

@@ -28,20 +28,12 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.runBlocking
/**
* The config used to load the App List.
*/
data class AppListConfig(
val userId: Int,
val showInstantApps: Boolean,
)
/**
* The repository to load the App List data.
*/
internal interface AppListRepository {
/** Loads the list of [ApplicationInfo]. */
suspend fun loadApps(config: AppListConfig): List<ApplicationInfo>
suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo>
/** Gets the flow of predicate that could used to filter system app. */
fun showSystemPredicate(
@@ -50,7 +42,7 @@ internal interface AppListRepository {
): Flow<(app: ApplicationInfo) -> Boolean>
/** Gets the system app package names. */
fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String>
fun getSystemPackageNamesBlocking(userId: Int, showInstantApps: Boolean): Set<String>
}
/**
@@ -59,15 +51,21 @@ internal interface AppListRepository {
object AppListRepositoryUtil {
/** Gets the system app package names. */
@JvmStatic
fun getSystemPackageNames(context: Context, config: AppListConfig): Set<String> {
return AppListRepositoryImpl(context).getSystemPackageNamesBlocking(config)
}
fun getSystemPackageNames(
context: Context,
userId: Int,
showInstantApps: Boolean,
): Set<String> =
AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId, showInstantApps)
}
internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
private val packageManager = context.packageManager
override suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> = coroutineScope {
override suspend fun loadApps(
userId: Int,
showInstantApps: Boolean,
): List<ApplicationInfo> = coroutineScope {
val hiddenSystemModulesDeferred = async {
packageManager.getInstalledModules(0)
.filter { it.isHidden }
@@ -82,12 +80,12 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
)
val installedApplicationsAsUser =
packageManager.getInstalledApplicationsAsUser(flags, config.userId)
packageManager.getInstalledApplicationsAsUser(flags, userId)
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
installedApplicationsAsUser.filter { app ->
app.isInAppList(config.showInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
app.isInAppList(showInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
}
}
@@ -97,18 +95,17 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo
): Flow<(app: ApplicationInfo) -> Boolean> =
userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
override fun getSystemPackageNamesBlocking(config: AppListConfig) = runBlocking {
getSystemPackageNames(config)
}
override fun getSystemPackageNamesBlocking(userId: Int, showInstantApps: Boolean) =
runBlocking { getSystemPackageNames(userId, showInstantApps) }
private suspend fun getSystemPackageNames(config: AppListConfig): Set<String> =
coroutineScope {
val loadAppsDeferred = async { loadApps(config) }
val homeOrLauncherPackages = loadHomeOrLauncherPackages(config.userId)
val showSystemPredicate =
{ app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
}
private suspend fun getSystemPackageNames(userId: Int, showInstantApps: Boolean): Set<String> =
coroutineScope {
val loadAppsDeferred = async { loadApps(userId, showInstantApps) }
val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
val showSystemPredicate =
{ app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
}
private suspend fun showSystemPredicate(
userId: Int,

View File

@@ -26,6 +26,7 @@ import com.android.settingslib.spa.framework.util.StateFlowBridge
import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spa.framework.util.waitFirst
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.template.app.AppListConfig
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,8 +35,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
@@ -78,32 +79,77 @@ internal open class AppListViewModelImpl<T : AppRecord>(
private val labelMap = ConcurrentHashMap<String, String>()
private val scope = viewModelScope + Dispatchers.IO
private val userIdFlow = appListConfig.flow.map { it.userId }
private val userSubGraphsFlow = appListConfig.flow.map { config ->
config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) }
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
private val appsStateFlow = MutableStateFlow<List<ApplicationInfo>?>(null)
private inner class UserSubGraph(
private val userId: Int,
private val showInstantApps: Boolean,
) {
private val userIdFlow = flowOf(userId)
private val recordListFlow = listModel.flow
.flatMapLatest { it.transform(userIdFlow, appsStateFlow.filterNotNull()) }
.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
private val appsStateFlow = MutableStateFlow<List<ApplicationInfo>?>(null)
private val systemFilteredFlow =
appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
.combine(recordListFlow) { showAppPredicate, recordList ->
recordList.filter { showAppPredicate(it.app) }
val recordListFlow = listModel.flow
.flatMapLatest { it.transform(userIdFlow, appsStateFlow.filterNotNull()) }
.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
private val systemFilteredFlow =
appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
.combine(recordListFlow) { showAppPredicate, recordList ->
recordList.filter { showAppPredicate(it.app) }
}
.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
val listModelFilteredFlow = optionFlow.filterNotNull().flatMapLatest { option ->
listModel.flow.flatMapLatest { listModel ->
listModel.filter(this.userIdFlow, option, this.systemFilteredFlow)
}
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
fun reloadApps() {
scope.launch {
appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps)
}
}
}
private val combinedRecordListFlow = userSubGraphsFlow.flatMapLatest { userSubGraphList ->
combine(userSubGraphList.map { it.recordListFlow }) { it.toList().flatten() }
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
override val spinnerOptionsFlow =
recordListFlow.combine(listModel.flow) { recordList, listModel ->
combinedRecordListFlow.combine(listModel.flow) { recordList, listModel ->
listModel.getSpinnerOptions(recordList)
}
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
override val appListDataFlow = optionFlow.filterNotNull().flatMapLatest(::filterAndSort)
.combine(searchQuery.flow) { appListData, searchQuery ->
private val appEntryListFlow = userSubGraphsFlow.flatMapLatest { userSubGraphList ->
combine(userSubGraphList.map { it.listModelFilteredFlow }) { it.toList().flatten() }
}.asyncMapItem { record ->
val label = getLabel(record.app)
AppEntry(
record = record,
label = label,
labelCollationKey = collator.getCollationKey(label),
)
}
override val appListDataFlow =
combine(
appEntryListFlow,
listModel.flow,
optionFlow.filterNotNull(),
) { appEntries, listModel, option ->
AppListData(
appEntries = appEntries.sortedWith(listModel.getComparator(option)),
option = option,
)
}.combine(searchQuery.flow) { appListData, searchQuery ->
appListData.filter {
it.label.contains(other = searchQuery, ignoreCase = true)
}
}
.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
init {
scheduleOnFirstLoaded()
@@ -111,30 +157,16 @@ internal open class AppListViewModelImpl<T : AppRecord>(
fun reloadApps() {
scope.launch {
appsStateFlow.value = appListRepository.loadApps(appListConfig.flow.first())
userSubGraphsFlow.collect { userSubGraphList ->
for (userSubGraph in userSubGraphList) {
userSubGraph.reloadApps()
}
}
}
}
private fun filterAndSort(option: Int) = listModel.flow.flatMapLatest { listModel ->
listModel.filter(userIdFlow, option, systemFilteredFlow)
.asyncMapItem { record ->
val label = getLabel(record.app)
AppEntry(
record = record,
label = label,
labelCollationKey = collator.getCollationKey(label),
)
}
.map { appEntries ->
AppListData(
appEntries = appEntries.sortedWith(listModel.getComparator(option)),
option = option,
)
}
}
private fun scheduleOnFirstLoaded() {
recordListFlow
combinedRecordListFlow
.waitFirst(appListDataFlow)
.combine(listModel.flow) { recordList, listModel ->
if (listModel.onFirstLoaded(recordList)) {

View File

@@ -32,6 +32,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
@@ -43,7 +44,6 @@ import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppListViewModel
@@ -56,6 +56,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
private const val TAG = "AppList"
private const val CONTENT_TYPE_HEADER = "header"
/**
* The config used to load the App List.
*/
data class AppListConfig(
val userIds: List<Int>,
val showInstantApps: Boolean,
)
data class AppListState(
val showSystem: State<Boolean>,
val searchQuery: State<String>,
@@ -84,7 +92,7 @@ fun <T : AppRecord> AppListInput<T>.AppList() {
internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
viewModelSupplier: @Composable () -> IAppListViewModel<T>,
) {
LogCompositions(TAG, config.userId.toString())
LogCompositions(TAG, config.userIds.toString())
val viewModel = viewModelSupplier()
Column(Modifier.fillMaxSize()) {
val optionsState = viewModel.spinnerOptionsFlow.collectAsState(null, Dispatchers.IO)
@@ -168,21 +176,23 @@ private fun <T : AppRecord> rememberViewModel(
listModel: AppListModel<T>,
state: AppListState,
): AppListViewModel<T> {
val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
val viewModel: AppListViewModel<T> = viewModel(key = config.userIds.toString())
viewModel.appListConfig.setIfAbsent(config)
viewModel.listModel.setIfAbsent(listModel)
viewModel.showSystem.Sync(state.showSystem)
viewModel.searchQuery.Sync(state.searchQuery)
DisposableBroadcastReceiverAsUser(
intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
addAction(Intent.ACTION_PACKAGE_REMOVED)
addAction(Intent.ACTION_PACKAGE_CHANGED)
addDataScheme("package")
},
userHandle = UserHandle.of(config.userId),
onStart = { viewModel.reloadApps() },
) { viewModel.reloadApps() }
LifecycleEffect(onStart = { viewModel.reloadApps() })
val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
addAction(Intent.ACTION_PACKAGE_REMOVED)
addAction(Intent.ACTION_PACKAGE_CHANGED)
addDataScheme("package")
}
for (userId in config.userIds) {
DisposableBroadcastReceiverAsUser(
intentFilter = intentFilter,
userHandle = UserHandle.of(userId),
) { viewModel.reloadApps() }
}
return viewModel
}
}

View File

@@ -24,10 +24,9 @@ import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
import com.android.settingslib.spa.widget.scaffold.SearchScaffold
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
import com.android.settingslib.spaprivileged.template.common.UserProfilePager
/**
* The full screen template for an App List page.
@@ -55,10 +54,10 @@ fun <T : AppRecord> AppListPage(
}
},
) { bottomPadding, searchQuery ->
WorkProfilePager(primaryUserOnly) { userInfo ->
UserProfilePager(primaryUserOnly) { userGroup ->
val appListInput = AppListInput(
config = AppListConfig(
userId = userInfo.id,
userIds = userGroup.userInfos.map { it.id },
showInstantApps = showInstantApps,
),
listModel = listModel,

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spaprivileged.template.common
import android.content.pm.UserInfo
import android.content.pm.UserProperties
import android.os.UserHandle
import android.os.UserManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.android.settingslib.spa.widget.scaffold.SettingsPager
import com.android.settingslib.spaprivileged.framework.common.userManager
import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository
/**
* Info about how to group multiple profiles for Settings.
*
* @see [UserProperties.ShowInSettings]
*/
data class UserGroup(
/** The users in this user group, if multiple users, the first one is parent user. */
val userInfos: List<UserInfo>,
)
@Composable
fun UserProfilePager(
primaryUserOnly: Boolean = false,
content: @Composable (userGroup: UserGroup) -> Unit,
) {
val context = LocalContext.current
val userGroups = remember {
context.userManager.getUserGroups(primaryUserOnly)
}
val titles = remember {
val enterpriseRepository = EnterpriseRepository(context)
userGroups.map { userGroup ->
enterpriseRepository.getProfileTitle(
isManagedProfile = userGroup.userInfos.first().isManagedProfile,
)
}
}
SettingsPager(titles) { page ->
content(userGroups[page])
}
}
private fun UserManager.getUserGroups(primaryUserOnly: Boolean): List<UserGroup> {
val userGroupList = mutableListOf<UserGroup>()
val profileToShowInSettingsList = getProfiles(UserHandle.myUserId())
.filter { userInfo -> !primaryUserOnly || userInfo.isPrimary }
.map { userInfo -> userInfo to getUserProperties(userInfo.userHandle).showInSettings }
profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT }
.takeIf { it.isNotEmpty() }
?.map { it.first }
?.let { userInfos -> userGroupList += UserGroup(userInfos) }
profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_LAUNCHER_SEPARATE }
.forEach { userGroupList += UserGroup(userInfos = listOf(it.first)) }
return userGroupList
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spaprivileged.template.common
import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.android.settingslib.spa.widget.scaffold.SettingsPager
import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository
@Composable
fun WorkProfilePager(
primaryUserOnly: Boolean = false,
content: @Composable (userInfo: UserInfo) -> Unit,
) {
val context = LocalContext.current
val profiles = remember {
val userManager = checkNotNull(context.getSystemService(UserManager::class.java))
userManager.getProfiles(UserHandle.myUserId()).filter { userInfo ->
!primaryUserOnly || userInfo.isPrimary
}
}
val titles = remember {
val enterpriseRepository = EnterpriseRepository(context)
profiles.map {
enterpriseRepository.getProfileTitle(isManagedProfile = it.isManagedProfile)
}
}
SettingsPager(titles) { page ->
content(profiles[page])
}
}

View File

@@ -85,23 +85,6 @@ class DisposableBroadcastReceiverAsUserTest {
assertThat(onReceiveIsCalled).isTrue()
}
@Test
fun broadcastReceiver_onStartIsCalled() {
var onStartIsCalled = false
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
DisposableBroadcastReceiverAsUser(
intentFilter = IntentFilter(),
userHandle = USER_HANDLE,
onStart = { onStartIsCalled = true },
onReceive = {},
)
}
}
assertThat(onStartIsCalled).isTrue()
}
private companion object {
val USER_HANDLE: UserHandle = UserHandle.of(0)
}

View File

@@ -83,9 +83,8 @@ class AppListRepositoryTest {
@Test
fun loadApps_notShowInstantApps() = runTest {
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).containsExactly(NORMAL_APP)
}
@@ -93,9 +92,8 @@ class AppListRepositoryTest {
@Test
fun loadApps_showInstantApps() = runTest {
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
val appListFlow = repository.loadApps(appListConfig)
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true)
assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
}
@@ -109,9 +107,8 @@ class AppListRepositoryTest {
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
mockInstalledApplications(listOf(app))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).isEmpty()
}
@@ -126,9 +123,8 @@ class AppListRepositoryTest {
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
mockInstalledApplications(listOf(app))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).isEmpty()
}
@@ -142,9 +138,8 @@ class AppListRepositoryTest {
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
mockInstalledApplications(listOf(app))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).containsExactly(app)
}
@@ -157,9 +152,8 @@ class AppListRepositoryTest {
enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
}
mockInstalledApplications(listOf(app))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).containsExactly(app)
}
@@ -171,9 +165,8 @@ class AppListRepositoryTest {
enabled = false
}
mockInstalledApplications(listOf(app))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).isEmpty()
}
@@ -223,7 +216,7 @@ class AppListRepositoryTest {
@Test
fun showSystemPredicate_appInLauncher() = runTest {
val app = IN_LAUMCHER_APP
val app = IN_LAUNCHER_APP
whenever(
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
@@ -237,10 +230,13 @@ class AppListRepositoryTest {
@Test
fun getSystemPackageNames_returnExpectedValues() = runTest {
mockInstalledApplications(listOf(
NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUMCHER_APP))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP))
val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(context, appListConfig)
val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
context = context,
userId = USER_ID,
showInstantApps = false,
)
assertThat(systemPackageNames).containsExactly("system.app", "home.app", "app.in.launcher")
}
@@ -280,7 +276,7 @@ class AppListRepositoryTest {
flags = ApplicationInfo.FLAG_SYSTEM
}
val IN_LAUMCHER_APP = ApplicationInfo().apply {
val IN_LAUNCHER_APP = ApplicationInfo().apply {
packageName = "app.in.launcher"
flags = ApplicationInfo.FLAG_SYSTEM
}

View File

@@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spa.testutils.waitUntil
import com.android.settingslib.spaprivileged.template.app.AppListConfig
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -84,14 +85,17 @@ class AppListViewModelTest {
}
private object FakeAppListRepository : AppListRepository {
override suspend fun loadApps(config: AppListConfig) = listOf(APP)
override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP)
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
override fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String> = setOf()
override fun getSystemPackageNamesBlocking(
userId: Int,
showInstantApps: Boolean,
): Set<String> = emptySet()
}
private object FakeAppRepository : AppRepository {
@@ -105,7 +109,7 @@ class AppListViewModelTest {
const val USER_ID = 0
const val PACKAGE_NAME = "package.name"
const val LABEL = "Label"
val CONFIG = AppListConfig(userId = USER_ID, showInstantApps = false)
val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false)
val APP = ApplicationInfo().apply {
packageName = PACKAGE_NAME
}

View File

@@ -32,7 +32,6 @@ import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
@@ -115,7 +114,7 @@ class AppListTest {
) {
composeTestRule.setContent {
AppListInput(
config = AppListConfig(userId = USER_ID, showInstantApps = false),
config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false),
listModel = TestAppListModel(enableGrouping = enableGrouping),
state = AppListState(
showSystem = false.toState(),