Merge "Display cloned app under person tab in App List"

This commit is contained in:
Chaohui Wang
2023-01-20 05:49:58 +00:00
committed by Android (Google) Code Review
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(),