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