1 /* <lambda>null2 * 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.FeatureFlags 23 import android.content.pm.FeatureFlagsImpl 24 import android.content.pm.Flags 25 import android.content.pm.PackageManager 26 import android.content.pm.PackageManager.ApplicationInfoFlags 27 import android.content.pm.ResolveInfo 28 import android.os.SystemProperties 29 import android.util.Log 30 import com.android.internal.R 31 import com.android.settingslib.spaprivileged.framework.common.userManager 32 import kotlinx.coroutines.async 33 import kotlinx.coroutines.awaitAll 34 import kotlinx.coroutines.coroutineScope 35 import kotlinx.coroutines.flow.Flow 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.runBlocking 38 39 /** 40 * The repository to load the App List data. 41 */ 42 interface AppListRepository { 43 /** Loads the list of [ApplicationInfo]. */ 44 suspend fun loadApps( 45 userId: Int, 46 loadInstantApps: Boolean = false, 47 matchAnyUserForAdmin: Boolean = false, 48 ): List<ApplicationInfo> 49 50 /** Gets the flow of predicate that could used to filter system app. */ 51 fun showSystemPredicate( 52 userIdFlow: Flow<Int>, 53 showSystemFlow: Flow<Boolean>, 54 ): Flow<(app: ApplicationInfo) -> Boolean> 55 56 /** Gets the system app package names. */ 57 fun getSystemPackageNamesBlocking(userId: Int): Set<String> 58 59 /** Loads the list of [ApplicationInfo], and filter base on `isSystemApp`. */ 60 suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean): List<ApplicationInfo> 61 } 62 63 /** 64 * Util for app list repository. 65 */ 66 object AppListRepositoryUtil { 67 /** Gets the system app package names. */ 68 @JvmStatic getSystemPackageNamesnull69 fun getSystemPackageNames(context: Context, userId: Int): Set<String> = 70 AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId) 71 } 72 73 /** 74 * This constructor is visible for tests only in order to override `featureFlags`. 75 */ 76 class AppListRepositoryImpl( 77 private val context: Context, 78 private val featureFlags: FeatureFlags 79 ) : AppListRepository { 80 private val packageManager = context.packageManager 81 private val userManager = context.userManager 82 83 constructor(context: Context) : this(context, FeatureFlagsImpl()) 84 85 override suspend fun loadApps( 86 userId: Int, 87 loadInstantApps: Boolean, 88 matchAnyUserForAdmin: Boolean, 89 ): List<ApplicationInfo> = try { 90 coroutineScope { 91 val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() } 92 val hideWhenDisabledPackagesDeferred = async { 93 context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames) 94 } 95 val installedApplicationsAsUser = 96 getInstalledApplications(userId, matchAnyUserForAdmin) 97 98 val hiddenSystemModules = hiddenSystemModulesDeferred.await() 99 val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await() 100 installedApplicationsAsUser.filter { app -> 101 app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages) 102 } 103 } 104 } catch (e: Exception) { 105 Log.e(TAG, "loadApps failed", e) 106 emptyList() 107 } 108 109 private suspend fun getInstalledApplications( 110 userId: Int, 111 matchAnyUserForAdmin: Boolean, 112 ): List<ApplicationInfo> { 113 val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or 114 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() 115 val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags)) 116 PackageManager.MATCH_ARCHIVED_PACKAGES else 0L 117 val regularFlags = ApplicationInfoFlags.of( 118 disabledComponentsFlag or 119 archivedPackagesFlag 120 ) 121 return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { 122 packageManager.getInstalledApplicationsAsUser(regularFlags, userId) 123 } else { 124 coroutineScope { 125 val deferredPackageNamesInChildProfiles = 126 userManager.getProfileIdsWithDisabled(userId) 127 .filter { it != userId } 128 .map { 129 async { 130 packageManager.getInstalledApplicationsAsUser(regularFlags, it) 131 .map { it.packageName } 132 } 133 } 134 val adminFlags = ApplicationInfoFlags.of( 135 PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value 136 ) 137 val allInstalledApplications = 138 packageManager.getInstalledApplicationsAsUser(adminFlags, userId) 139 val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles 140 .awaitAll() 141 .flatten() 142 .toSet() 143 // If an app is for a child profile and not installed on the owner, not display as 144 // 'not installed for this user' in the owner. This will prevent duplicates of work 145 // only apps showing up in the personal profile. 146 allInstalledApplications.filter { 147 it.installed || it.packageName !in packageNamesInChildProfiles 148 } 149 } 150 } 151 } 152 153 private fun isArchivingEnabled(featureFlags: FeatureFlags) = 154 featureFlags.archiving() 155 156 override fun showSystemPredicate( 157 userIdFlow: Flow<Int>, 158 showSystemFlow: Flow<Boolean>, 159 ): Flow<(app: ApplicationInfo) -> Boolean> = 160 userIdFlow.combine(showSystemFlow, ::showSystemPredicate) 161 162 override fun getSystemPackageNamesBlocking(userId: Int) = runBlocking { 163 loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet() 164 } 165 166 override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope { 167 val loadAppsDeferred = async { loadApps(userId) } 168 val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) 169 loadAppsDeferred.await().filter { app -> 170 isSystemApp(app, homeOrLauncherPackages) == isSystemApp 171 } 172 } 173 174 private suspend fun showSystemPredicate( 175 userId: Int, 176 showSystem: Boolean, 177 ): (app: ApplicationInfo) -> Boolean { 178 if (showSystem) return { true } 179 val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId) 180 return { app -> !isSystemApp(app, homeOrLauncherPackages) } 181 } 182 183 private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> { 184 val launchIntent = Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER) 185 // If we do not specify MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE, system will 186 // derive and update the flags according to the user's lock state. When the user is locked, 187 // components with ComponentInfo#directBootAware == false will be filtered. We should 188 // explicitly include both direct boot aware and unaware component here. 189 val flags = PackageManager.ResolveInfoFlags.of( 190 (PackageManager.MATCH_DISABLED_COMPONENTS or 191 PackageManager.MATCH_DIRECT_BOOT_AWARE or 192 PackageManager.MATCH_DIRECT_BOOT_UNAWARE).toLong() 193 ) 194 return coroutineScope { 195 val launcherActivities = async { 196 packageManager.queryIntentActivitiesAsUser(launchIntent, flags, userId) 197 } 198 val homeActivities = ArrayList<ResolveInfo>() 199 packageManager.getHomeActivities(homeActivities) 200 (launcherActivities.await() + homeActivities) 201 .map { it.activityInfo.packageName } 202 .toSet() 203 } 204 } 205 206 private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean = 207 app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages 208 209 private fun PackageManager.getHiddenSystemModules(): Set<String> { 210 val moduleInfos = getInstalledModules(0).filter { it.isHidden } 211 val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet() 212 if (Flags.provideInfoOfApkInApex()) { 213 hiddenApps += moduleInfos.flatMap { it.apkInApexPackageNames } 214 } 215 return hiddenApps 216 } 217 218 companion object { 219 private const val TAG = "AppListRepository" 220 221 private fun ApplicationInfo.isInAppList( 222 showInstantApps: Boolean, 223 hiddenSystemModules: Set<String>, 224 hideWhenDisabledPackages: Array<String>, 225 ) = when { 226 !showInstantApps && isInstantApp -> false 227 packageName in hiddenSystemModules -> false 228 packageName in hideWhenDisabledPackages -> enabled && !isDisabledUntilUsed 229 enabled -> true 230 else -> enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER 231 } 232 } 233 } 234