Merge "Display cloned app under person tab in App List"
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