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