• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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