1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib.spaprivileged.model.app 18 19 import android.content.Context 20 import android.content.Intent 21 import android.content.pm.ApplicationInfo 22 import android.content.pm.PackageManager 23 import android.content.pm.PackageManager.ApplicationInfoFlags 24 import android.content.pm.ResolveInfo 25 import com.android.internal.R 26 import com.android.settingslib.spaprivileged.framework.common.userManager 27 import kotlinx.coroutines.async 28 import kotlinx.coroutines.awaitAll 29 import kotlinx.coroutines.coroutineScope 30 import kotlinx.coroutines.flow.Flow 31 import kotlinx.coroutines.flow.combine 32 import kotlinx.coroutines.runBlocking 33 34 /** 35 * The repository to load the App List data. 36 */ 37 interface AppListRepository { 38 /** Loads the list of [ApplicationInfo]. */ 39 suspend fun loadApps( 40 userId: Int, 41 loadInstantApps: Boolean = false, 42 matchAnyUserForAdmin: Boolean = false, 43 ): List<ApplicationInfo> 44 45 /** Gets the flow of predicate that could used to filter system app. */ 46 fun showSystemPredicate( 47 userIdFlow: Flow<Int>, 48 showSystemFlow: Flow<Boolean>, 49 ): Flow<(app: ApplicationInfo) -> Boolean> 50 51 /** Gets the system app package names. */ 52 fun getSystemPackageNamesBlocking(userId: Int): Set<String> 53 54 /** Loads the list of [ApplicationInfo], and filter base on `isSystemApp`. */ 55 suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean): List<ApplicationInfo> 56 } 57 58 /** 59 * Util for app list repository. 60 */ 61 object AppListRepositoryUtil { 62 /** Gets the system app package names. */ 63 @JvmStatic 64 fun getSystemPackageNames(context: Context, userId: Int): Set<String> = 65 AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId) 66 } 67 68 class AppListRepositoryImpl(private val context: Context) : AppListRepository { 69 private val packageManager = context.packageManager 70 private val userManager = context.userManager 71 72 override suspend fun loadApps( 73 userId: Int, 74 loadInstantApps: Boolean, 75 matchAnyUserForAdmin: Boolean, 76 ): List<ApplicationInfo> = coroutineScope { 77 val hiddenSystemModulesDeferred = async { 78 packageManager.getInstalledModules(0) 79 .filter { it.isHidden } 80 .map { it.packageName } 81 .toSet() 82 } 83 val hideWhenDisabledPackagesDeferred = async { 84 context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames) 85 } 86 val installedApplicationsAsUser = 87 getInstalledApplications(userId, matchAnyUserForAdmin) 88 89 val hiddenSystemModules = hiddenSystemModulesDeferred.await() 90 val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await() 91 installedApplicationsAsUser.filter { app -> 92 app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages) 93 } 94 } 95 96 private suspend fun getInstalledApplications( 97 userId: Int, 98 matchAnyUserForAdmin: Boolean, 99 ): List<ApplicationInfo> { 100 val regularFlags = ApplicationInfoFlags.of( 101 (PackageManager.MATCH_DISABLED_COMPONENTS or 102 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() 103 ) 104 return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { 105 packageManager.getInstalledApplicationsAsUser(regularFlags, userId) 106 } else { 107 coroutineScope { 108 val deferredPackageNamesInChildProfiles = 109 userManager.getProfileIdsWithDisabled(userId) 110 .filter { it != userId } 111 .map { 112 async { 113 packageManager.getInstalledApplicationsAsUser(regularFlags, it) 114 .map { it.packageName } 115 } 116 } 117 val adminFlags = ApplicationInfoFlags.of( 118 PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value 119 ) 120 val allInstalledApplications = 121 packageManager.getInstalledApplicationsAsUser(adminFlags, userId) 122 val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles 123 .awaitAll() 124 .flatten() 125 .toSet() 126 // If an app is for a child profile and not installed on the owner, not display as 127 // 'not installed for this user' in the owner. This will prevent duplicates of work 128 // only apps showing up in the personal profile. 129 allInstalledApplications.filter { 130 it.installed || it.packageName !in packageNamesInChildProfiles 131 } 132 } 133 } 134 } 135 136 override fun showSystemPredicate( 137 userIdFlow: Flow<Int>, 138 showSystemFlow: Flow<Boolean>, 139 ): Flow<(app: ApplicationInfo) -> Boolean> = 140 userIdFlow.combine(showSystemFlow, ::showSystemPredicate) 141 142 override fun getSystemPackageNamesBlocking(userId: Int) = runBlocking { 143 loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet() 144 } 145 146 override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope { 147 val loadAppsDeferred = async { loadApps(userId) } 148 val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) 149 loadAppsDeferred.await().filter { app -> 150 isSystemApp(app, homeOrLauncherPackages) == isSystemApp 151 } 152 } 153 154 private suspend fun showSystemPredicate( 155 userId: Int, 156 showSystem: Boolean, 157 ): (app: ApplicationInfo) -> Boolean { 158 if (showSystem) return { true } 159 val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) 160 return { app -> !isSystemApp(app, homeOrLauncherPackages) } 161 } 162 163 private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { 164 val launchIntent = Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER) 165 // If we do not specify MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE, system will 166 // derive and update the flags according to the user's lock state. When the user is locked, 167 // components with ComponentInfo#directBootAware == false will be filtered. We should 168 // explicitly include both direct boot aware and unaware component here. 169 val flags = PackageManager.ResolveInfoFlags.of( 170 (PackageManager.MATCH_DISABLED_COMPONENTS or 171 PackageManager.MATCH_DIRECT_BOOT_AWARE or 172 PackageManager.MATCH_DIRECT_BOOT_UNAWARE).toLong() 173 ) 174 return coroutineScope { 175 val launcherActivities = async { 176 packageManager.queryIntentActivitiesAsUser(launchIntent, flags, userId) 177 } 178 val homeActivities = ArrayList<ResolveInfo>() 179 packageManager.getHomeActivities(homeActivities) 180 (launcherActivities.await() + homeActivities) 181 .map { it.activityInfo.packageName } 182 .toSet() 183 } 184 } 185 186 private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean = 187 app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages 188 189 companion object { 190 private fun ApplicationInfo.isInAppList( 191 showInstantApps: Boolean, 192 hiddenSystemModules: Set<String>, 193 hideWhenDisabledPackages: Array<String>, 194 ) = when { 195 !showInstantApps && isInstantApp -> false 196 packageName in hiddenSystemModules -> false 197 packageName in hideWhenDisabledPackages -> enabled && !isDisabledUntilUsed 198 enabled -> true 199 else -> enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER 200 } 201 } 202 } 203