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