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.content.pm.ApplicationInfo
20 import androidx.compose.material.icons.Icons
21 import androidx.compose.material.icons.outlined.ArrowCircleDown
22 import androidx.compose.material.icons.outlined.HideSource
23 import androidx.compose.material3.Text
24 import androidx.compose.runtime.Composable
25 import androidx.compose.ui.res.stringResource
26 import com.android.settings.R
27 import com.android.settings.Utils
28 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
29 import com.android.settingslib.spa.widget.button.ActionButton
30 import com.android.settingslib.spa.widget.dialog.AlertDialogButton
31 import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
32 import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
33 import com.android.settingslib.spaprivileged.framework.common.userManager
34 import com.android.settingslib.spaprivileged.model.app.isDisabledUntilUsed
35 import com.android.settingslib.Utils as SettingsLibUtils
36 
37 class AppDisableButton(
38     private val packageInfoPresenter: PackageInfoPresenter,
39 ) {
40     private val context = packageInfoPresenter.context
41     private val appButtonRepository = AppButtonRepository(context)
42     private val resources = context.resources
43     private val packageManager = context.packageManager
44     private val userManager = context.userManager
45     private val devicePolicyManager = context.devicePolicyManager
46     private val applicationFeatureProvider = featureFactory.applicationFeatureProvider
47 
48     @Composable
getActionButtonnull49     fun getActionButton(app: ApplicationInfo): ActionButton? {
50         if (!app.isSystemApp) return null
51 
52         return when {
53             app.enabled && !app.isDisabledUntilUsed -> {
54                 disableButton(app)
55             }
56 
57             else -> enableButton()
58         }
59     }
60 
61     /**
62      * Gets whether a package can be disabled.
63      */
canBeDisablednull64     private fun ApplicationInfo.canBeDisabled(): Boolean = when {
65         // Try to prevent the user from bricking their phone by not allowing disabling of apps
66         // signed with the system certificate.
67         isSignedWithPlatformKey -> false
68 
69         // system/vendor resource overlays can never be disabled.
70         isResourceOverlay -> false
71 
72         packageName in applicationFeatureProvider.keepEnabledPackages -> false
73 
74         // Home launcher apps need special handling. In system ones we don't risk downgrading
75         // because that can interfere with home-key resolution.
76         packageName in appButtonRepository.getHomePackageInfo().homePackages -> false
77 
78         SettingsLibUtils.isEssentialPackage(resources, packageManager, packageName) -> false
79 
80         // We don't allow disabling DO/PO on *any* users if it's a system app, because
81         // "disabling" is actually "downgrade to the system version + disable", and "downgrade"
82         // will clear data on all users.
83         Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, packageName) -> false
84 
85         appButtonRepository.isDisallowControl(this) -> false
86 
87         else -> true
88     }
89 
90     @Composable
disableButtonnull91     private fun disableButton(app: ApplicationInfo): ActionButton {
92         val dialogPresenter = confirmDialogPresenter()
93         return ActionButton(
94             text = context.getString(R.string.disable_text),
95             imageVector = Icons.Outlined.HideSource,
96             enabled = app.canBeDisabled(),
97         ) {
98             // Currently we apply the same device policy for both the uninstallation and disable
99             // button.
100             if (!appButtonRepository.isUninstallBlockedByAdmin(app)) {
101                 dialogPresenter.open()
102             }
103         }
104     }
105 
enableButtonnull106     private fun enableButton() = ActionButton(
107         text = context.getString(R.string.enable_text),
108         imageVector = Icons.Outlined.ArrowCircleDown,
109     ) { packageInfoPresenter.enable() }
110 
111     @Composable
confirmDialogPresenternull112     private fun confirmDialogPresenter() = rememberAlertDialogPresenter(
113         confirmButton = AlertDialogButton(
114             text = stringResource(R.string.app_disable_dlg_positive),
115             onClick = packageInfoPresenter::disable,
116         ),
117         dismissButton = AlertDialogButton(stringResource(R.string.cancel)),
118         text = { Text(stringResource(R.string.app_disable_dlg_text)) },
119     )
120 }
121