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.settings.spa.app.appinfo 18 19 import android.app.settings.SettingsEnums 20 import android.content.Intent 21 import android.content.om.OverlayManager 22 import android.content.pm.ApplicationInfo 23 import android.os.UserHandle 24 import android.os.UserManager 25 import androidx.compose.runtime.Composable 26 import androidx.compose.ui.graphics.vector.ImageVector 27 import androidx.compose.ui.res.vectorResource 28 import com.android.settings.R 29 import com.android.settings.Utils 30 import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd 31 import com.android.settingslib.spa.widget.button.ActionButton 32 import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager 33 import com.android.settingslib.spaprivileged.model.app.hasFlag 34 import com.android.settingslib.spaprivileged.model.app.isActiveAdmin 35 import com.android.settingslib.spaprivileged.model.app.userHandle 36 37 class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter) { 38 private val context = packageInfoPresenter.context 39 private val appButtonRepository = AppButtonRepository(context) 40 private val overlayManager = context.getSystemService(OverlayManager::class.java)!! 41 private val userManager = context.getSystemService(UserManager::class.java)!! 42 43 @Composable getActionButtonnull44 fun getActionButton(app: ApplicationInfo): ActionButton? { 45 if (app.isSystemApp || app.isInstantApp) return null 46 return uninstallButton(app = app, enabled = isUninstallButtonEnabled(app)) 47 } 48 49 /** Gets whether a package can be uninstalled. */ isUninstallButtonEnablednull50 private fun isUninstallButtonEnabled(app: ApplicationInfo): Boolean = when { 51 !app.hasFlag(ApplicationInfo.FLAG_INSTALLED) -> false 52 53 Utils.isProfileOrDeviceOwner( 54 context.devicePolicyManager, app.packageName, packageInfoPresenter.userId) -> false 55 56 appButtonRepository.isDisallowControl(app) -> false 57 58 uninstallDisallowedDueToHomeApp(app.packageName) -> false 59 60 // Resource overlays can be uninstalled iff they are public (installed on /data) and 61 // disabled. ("Enabled" means they are in use by resource management.) 62 app.isEnabledResourceOverlay() -> false 63 64 else -> true 65 } 66 67 /** 68 * Checks whether the given package cannot be uninstalled due to home app restrictions. 69 * 70 * Home launcher apps need special handling, we can't allow uninstallation of the only home 71 * app, and we don't want to allow uninstallation of an explicitly preferred one -- the user 72 * can go to Home settings and pick a different one, after which we'll permit uninstallation 73 * of the now-not-default one. 74 */ uninstallDisallowedDueToHomeAppnull75 private fun uninstallDisallowedDueToHomeApp(packageName: String): Boolean { 76 val homePackageInfo = appButtonRepository.getHomePackageInfo() 77 return when { 78 packageName !in homePackageInfo.homePackages -> false 79 80 // Disallow uninstall when this is the only home app. 81 homePackageInfo.homePackages.size == 1 -> true 82 83 // Disallow if this is the explicit default home app. 84 else -> packageName == homePackageInfo.currentDefaultHome?.packageName 85 } 86 } 87 ApplicationInfonull88 private fun ApplicationInfo.isEnabledResourceOverlay(): Boolean = 89 isResourceOverlay && 90 overlayManager.getOverlayInfo(packageName, userHandle)?.isEnabled == true 91 92 @Composable 93 private fun uninstallButton(app: ApplicationInfo, enabled: Boolean) = ActionButton( 94 text = if (isCloneApp(app)) context.getString(R.string.delete) else 95 context.getString(R.string.uninstall_text), 96 imageVector = ImageVector.vectorResource(R.drawable.ic_settings_delete), 97 enabled = enabled, 98 ) { onUninstallClicked(app) } 99 onUninstallClickednull100 private fun onUninstallClicked(app: ApplicationInfo) { 101 if (appButtonRepository.isUninstallBlockedByAdmin(app)) { 102 return 103 } else if (app.isActiveAdmin(context)) { 104 val uninstallDaIntent = Intent(context, DeviceAdminAdd::class.java) 105 uninstallDaIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, 106 app.packageName) 107 packageInfoPresenter.logAction( 108 SettingsEnums.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN) 109 context.startActivityAsUser(uninstallDaIntent, app.userHandle) 110 return 111 } 112 packageInfoPresenter.startUninstallActivity() 113 } 114 isCloneAppnull115 private fun isCloneApp(app: ApplicationInfo): Boolean { 116 val userInfo = userManager.getUserInfo(UserHandle.getUserId(app.uid)) 117 return userInfo != null && userInfo.isCloneProfile 118 } 119 } 120