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.settings.spa.app.appinfo
18 
19 import android.app.ActivityManager
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.om.OverlayManager
23 import android.content.pm.ApplicationInfo
24 import android.content.pm.Flags
25 import android.content.pm.PackageManager
26 import android.content.pm.ResolveInfo
27 import android.util.Log
28 import com.android.settingslib.RestrictedLockUtils
29 import com.android.settingslib.RestrictedLockUtilsInternal
30 import com.android.settingslib.Utils
31 import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
32 import com.android.settingslib.spaprivileged.model.app.hasFlag
33 import com.android.settingslib.spaprivileged.model.app.isDisallowControl
34 import com.android.settingslib.spaprivileged.model.app.userHandle
35 import com.android.settingslib.spaprivileged.model.app.userId
36 
37 class AppButtonRepository(private val context: Context) {
38     private val packageManager = context.packageManager
39     private val devicePolicyManager = context.devicePolicyManager
40 
41     /**
42      * Checks whether the given application is disallowed from modifying.
43      */
44     fun isDisallowControl(app: ApplicationInfo): Boolean = when {
45         // Not allow to control the device provisioning package.
46         Utils.isDeviceProvisioningPackage(context.resources, app.packageName) -> true
47 
48         // If the uninstallation intent is already queued, disable the button.
49         devicePolicyManager.isUninstallInQueue(app.packageName) -> true
50 
51         else -> app.isDisallowControl(context)
52     }
53 
54     /**
55      * Checks whether uninstall is blocked by admin.
56      */
57     fun isUninstallBlockedByAdmin(app: ApplicationInfo): Boolean =
58         RestrictedLockUtilsInternal.checkIfUninstallBlocked(context, app.packageName, app.userId)
59             ?.let { admin ->
60                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin)
61                 true
62             } ?: false
63 
64     fun getHomePackageInfo(): HomePackages {
65         val homePackages = mutableSetOf<String>()
66         val homeActivities = ArrayList<ResolveInfo>()
67         val currentDefaultHome = packageManager.getHomeActivities(homeActivities)
68         homeActivities.mapNotNull { it.activityInfo }.forEach { activityInfo ->
69             homePackages.add(activityInfo.packageName)
70             // Also make sure to include anything proxying for the home app
71             activityInfo.metaData?.getString(ActivityManager.META_HOME_ALTERNATE)
72                 ?.takeIf { signaturesMatch(it, activityInfo.packageName) }
73                 ?.let { homePackages.add(it) }
74         }
75         return HomePackages(homePackages, currentDefaultHome)
76     }
77 
78     private fun signaturesMatch(packageName1: String, packageName2: String): Boolean = try {
79         packageManager.checkSignatures(packageName1, packageName2) >= PackageManager.SIGNATURE_MATCH
80     } catch (e: Exception) {
81         // e.g. named alternate package not found during lookup; this is an expected case sometimes
82         false
83     }
84 
85     /** Gets whether a package can be uninstalled or archived. */
86     fun isAllowUninstallOrArchive(
87         context: Context, app: ApplicationInfo
88     ): Boolean {
89         val overlayManager = checkNotNull(context.getSystemService(OverlayManager::class.java))
90         when {
91             !app.hasFlag(ApplicationInfo.FLAG_INSTALLED) && !app.isArchived -> return false
92 
93             com.android.settings.Utils.isProfileOrDeviceOwner(
94                 context.devicePolicyManager, app.packageName, app.userId
95             ) -> return false
96 
97             isDisallowControl(app) -> return false
98 
99             uninstallDisallowedDueToHomeApp(app) -> return false
100 
101             // Resource overlays can be uninstalled iff they are public (installed on /data) and
102             // disabled. ("Enabled" means they are in use by resource management.)
103             app.isEnabledResourceOverlay(overlayManager) -> return false
104 
105             else -> return true
106         }
107     }
108 
109     /**
110      * Checks whether the given package cannot be uninstalled due to home app restrictions.
111      *
112      * Home launcher apps need special handling, we can't allow uninstallation of the only home
113      * app, and we don't want to allow uninstallation of an explicitly preferred one -- the user
114      * can go to Home settings and pick a different one, after which we'll permit uninstallation
115      * of the now-not-default one.
116      */
117     fun uninstallDisallowedDueToHomeApp(applicationInfo: ApplicationInfo): Boolean {
118         val packageName = applicationInfo.packageName
119         val homePackageInfo = getHomePackageInfo()
120         return when {
121             packageName !in homePackageInfo.homePackages -> false
122 
123             // Disallow uninstall when this is the only home app.
124             homePackageInfo.homePackages.size == 1 -> true
125 
126             packageName == homePackageInfo.currentDefaultHome?.packageName -> {
127                 if (Flags.improveHomeAppBehavior()) {
128                     // Disallow the uninstallation of the current home app if it is a system app.
129                     return applicationInfo.isSystemApp()
130                 } else {
131                     // Disallow if this is the explicit default home app.
132                     return true
133                 }
134             }
135 
136             else -> false
137         }
138     }
139 
140     private fun ApplicationInfo.isEnabledResourceOverlay(overlayManager: OverlayManager): Boolean =
141         isResourceOverlay &&
142             overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true
143 
144     data class HomePackages(
145         val homePackages: Set<String>,
146         val currentDefaultHome: ComponentName?,
147     )
148 }
149