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:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user