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