Loading src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt 0 → 100644 +78 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.app.ActivityManager import android.app.admin.DevicePolicyManager import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.os.UserManager import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.Utils import com.android.settingslib.spaprivileged.model.app.userId class AppButtonRepository(private val context: Context) { private val packageManager = context.packageManager private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!! /** * Checks whether the given application is disallowed from modifying. */ fun isDisallowControl(app: ApplicationInfo): Boolean = when { // Not allow to control the device provisioning package. Utils.isDeviceProvisioningPackage(context.resources, app.packageName) -> true // If the uninstallation intent is already queued, disable the button. devicePolicyManager.isUninstallInQueue(app.packageName) -> true RestrictedLockUtilsInternal.hasBaseUserRestriction( context, UserManager.DISALLOW_APPS_CONTROL, app.userId ) -> true else -> false } /** * Checks whether the given application is an active admin. */ fun isActiveAdmin(app: ApplicationInfo): Boolean = devicePolicyManager.packageHasActiveAdmins(app.packageName, app.userId) fun getHomePackageInfo(): AppUninstallButton.HomePackages { val homePackages = mutableSetOf<String>() val homeActivities = ArrayList<ResolveInfo>() val currentDefaultHome = packageManager.getHomeActivities(homeActivities) homeActivities.map { it.activityInfo }.forEach { homePackages.add(it.packageName) // Also make sure to include anything proxying for the home app val metaPackageName = it.metaData?.getString(ActivityManager.META_HOME_ALTERNATE) if (metaPackageName != null && signaturesMatch(metaPackageName, it.packageName)) { homePackages.add(metaPackageName) } } return AppUninstallButton.HomePackages(homePackages, currentDefaultHome) } private fun signaturesMatch(packageName1: String, packageName2: String): Boolean = try { packageManager.checkSignatures(packageName1, packageName2) >= PackageManager.SIGNATURE_MATCH } catch (e: Exception) { // e.g. named alternate package not found during lookup; this is an expected case sometimes false } } src/com/android/settings/spa/app/appsettings/AppButtons.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.content.pm.PackageInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spa.widget.button.ActionButtons import kotlinx.coroutines.flow.map @Composable fun AppButtons(packageInfoPresenter: PackageInfoPresenter) { val appButtonsHolder = remember { AppButtonsHolder(packageInfoPresenter) } appButtonsHolder.Dialogs() ActionButtons(actionButtons = appButtonsHolder.rememberActionsButtons().value) } private class AppButtonsHolder(private val packageInfoPresenter: PackageInfoPresenter) { private val appLaunchButton = AppLaunchButton(context = packageInfoPresenter.context) private val appDisableButton = AppDisableButton(packageInfoPresenter) private val appUninstallButton = AppUninstallButton(packageInfoPresenter) private val appForceStopButton = AppForceStopButton(packageInfoPresenter) @Composable fun rememberActionsButtons() = remember { packageInfoPresenter.flow.map { packageInfo -> if (packageInfo != null) getActionButtons(packageInfo) else emptyList() } }.collectAsState(initial = emptyList()) private fun getActionButtons(packageInfo: PackageInfo): List<ActionButton> = listOfNotNull( appLaunchButton.getActionButton(packageInfo), appDisableButton.getActionButton(packageInfo), appUninstallButton.getActionButton(packageInfo), appForceStopButton.getActionButton(packageInfo), ) @Composable fun Dialogs() { appDisableButton.DisableConfirmDialog() appForceStopButton.ForceStopConfirmDialog() } } src/com/android/settings/spa/app/appsettings/AppDisableButton.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.app.admin.DevicePolicyManager import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.os.UserManager import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowCircleDown import androidx.compose.material.icons.outlined.HideSource import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.android.settings.R import com.android.settings.Utils import com.android.settings.overlay.FeatureFactory import com.android.settingslib.Utils as SettingsLibUtils import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spaprivileged.model.app.hasFlag import com.android.settingslib.spaprivileged.model.app.isDisabledUntilUsed class AppDisableButton( private val packageInfoPresenter: PackageInfoPresenter, ) { private val context = packageInfoPresenter.context private val appButtonRepository = AppButtonRepository(context) private val resources = context.resources private val packageManager = context.packageManager private val userManager = context.getSystemService(UserManager::class.java)!! private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!! private val applicationFeatureProvider = FeatureFactory.getFactory(context).getApplicationFeatureProvider(context) private var openConfirmDialog by mutableStateOf(false) fun getActionButton(packageInfo: PackageInfo): ActionButton? { val app = packageInfo.applicationInfo if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) return null return when { app.enabled && !app.isDisabledUntilUsed() -> { disableButton(enabled = isDisableButtonEnabled(packageInfo)) } else -> enableButton() } } /** * Gets whether a package can be disabled. */ private fun isDisableButtonEnabled(packageInfo: PackageInfo): Boolean { val packageName = packageInfo.packageName val app = packageInfo.applicationInfo return when { packageName in applicationFeatureProvider.keepEnabledPackages -> false // Home launcher apps need special handling. In system ones we don't risk downgrading // because that can interfere with home-key resolution. packageName in appButtonRepository.getHomePackageInfo().homePackages -> false // Try to prevent the user from bricking their phone by not allowing disabling of apps // signed with the system certificate. SettingsLibUtils.isSystemPackage(resources, packageManager, packageInfo) -> false // If this is a device admin, it can't be disabled. appButtonRepository.isActiveAdmin(app) -> false // We don't allow disabling DO/PO on *any* users if it's a system app, because // "disabling" is actually "downgrade to the system version + disable", and "downgrade" // will clear data on all users. Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, packageName) -> false appButtonRepository.isDisallowControl(app) -> false // system/vendor resource overlays can never be disabled. app.isResourceOverlay -> false else -> true } } private fun disableButton(enabled: Boolean) = ActionButton( text = context.getString(R.string.disable_text), imageVector = Icons.Outlined.HideSource, enabled = enabled, ) { openConfirmDialog = true } private fun enableButton() = ActionButton( text = context.getString(R.string.enable_text), imageVector = Icons.Outlined.ArrowCircleDown, ) { packageInfoPresenter.enable() } @Composable fun DisableConfirmDialog() { if (!openConfirmDialog) return AlertDialog( onDismissRequest = { openConfirmDialog = false }, confirmButton = { TextButton( onClick = { openConfirmDialog = false packageInfoPresenter.disable() }, ) { Text(stringResource(R.string.app_disable_dlg_positive)) } }, dismissButton = { TextButton(onClick = { openConfirmDialog = false }) { Text(stringResource(R.string.cancel)) } }, text = { Text(stringResource(R.string.app_disable_dlg_text)) }, ) } } src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.app.settings.SettingsEnums import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.os.UserManager import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.android.settings.R import com.android.settingslib.RestrictedLockUtils import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spaprivileged.model.app.hasFlag import com.android.settingslib.spaprivileged.model.app.userId class AppForceStopButton( private val packageInfoPresenter: PackageInfoPresenter, ) { private val context = packageInfoPresenter.context private val appButtonRepository = AppButtonRepository(context) private val packageManager = context.packageManager private var openConfirmDialog by mutableStateOf(false) fun getActionButton(packageInfo: PackageInfo): ActionButton { val app = packageInfo.applicationInfo return ActionButton( text = context.getString(R.string.force_stop), imageVector = Icons.Outlined.WarningAmber, enabled = isForceStopButtonEnable(app), ) { onForceStopButtonClicked(app) } } /** * Gets whether a package can be force stopped. */ private fun isForceStopButtonEnable(app: ApplicationInfo): Boolean = when { // User can't force stop device admin. appButtonRepository.isActiveAdmin(app) -> false appButtonRepository.isDisallowControl(app) -> false // If the app isn't explicitly stopped, then always show the force stop button. else -> !app.hasFlag(ApplicationInfo.FLAG_STOPPED) } private fun onForceStopButtonClicked(app: ApplicationInfo) { packageInfoPresenter.logAction(SettingsEnums.ACTION_APP_INFO_FORCE_STOP) getAdminRestriction(app)?.let { admin -> RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin) return } openConfirmDialog = true } private fun getAdminRestriction(app: ApplicationInfo): EnforcedAdmin? = when { packageManager.isPackageStateProtected(app.packageName, app.userId) -> { RestrictedLockUtilsInternal.getDeviceOwner(context) } else -> RestrictedLockUtilsInternal.checkIfRestrictionEnforced( context, UserManager.DISALLOW_APPS_CONTROL, app.userId ) } @Composable fun ForceStopConfirmDialog() { if (!openConfirmDialog) return AlertDialog( onDismissRequest = { openConfirmDialog = false }, confirmButton = { TextButton( onClick = { openConfirmDialog = false packageInfoPresenter.forceStop() }, ) { Text(stringResource(R.string.okay)) } }, dismissButton = { TextButton(onClick = { openConfirmDialog = false }) { Text(stringResource(R.string.cancel)) } }, title = { Text(stringResource(R.string.force_stop_dlg_title)) }, text = { Text(stringResource(R.string.force_stop_dlg_text)) }, ) } } src/com/android/settings/spa/app/appsettings/AppLaunchButton.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Launch import com.android.settings.R import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spaprivileged.model.app.userHandle class AppLaunchButton(private val context: Context) { private val packageManager = context.packageManager fun getActionButton(packageInfo: PackageInfo): ActionButton? = packageManager.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent -> launchButton(intent, packageInfo.applicationInfo) } private fun launchButton(intent: Intent, app: ApplicationInfo) = ActionButton( text = context.getString(R.string.launch_instant_app), imageVector = Icons.Outlined.Launch, ) { context.startActivityAsUser(intent, app.userHandle) } } Loading
src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt 0 → 100644 +78 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.app.ActivityManager import android.app.admin.DevicePolicyManager import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.os.UserManager import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.Utils import com.android.settingslib.spaprivileged.model.app.userId class AppButtonRepository(private val context: Context) { private val packageManager = context.packageManager private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!! /** * Checks whether the given application is disallowed from modifying. */ fun isDisallowControl(app: ApplicationInfo): Boolean = when { // Not allow to control the device provisioning package. Utils.isDeviceProvisioningPackage(context.resources, app.packageName) -> true // If the uninstallation intent is already queued, disable the button. devicePolicyManager.isUninstallInQueue(app.packageName) -> true RestrictedLockUtilsInternal.hasBaseUserRestriction( context, UserManager.DISALLOW_APPS_CONTROL, app.userId ) -> true else -> false } /** * Checks whether the given application is an active admin. */ fun isActiveAdmin(app: ApplicationInfo): Boolean = devicePolicyManager.packageHasActiveAdmins(app.packageName, app.userId) fun getHomePackageInfo(): AppUninstallButton.HomePackages { val homePackages = mutableSetOf<String>() val homeActivities = ArrayList<ResolveInfo>() val currentDefaultHome = packageManager.getHomeActivities(homeActivities) homeActivities.map { it.activityInfo }.forEach { homePackages.add(it.packageName) // Also make sure to include anything proxying for the home app val metaPackageName = it.metaData?.getString(ActivityManager.META_HOME_ALTERNATE) if (metaPackageName != null && signaturesMatch(metaPackageName, it.packageName)) { homePackages.add(metaPackageName) } } return AppUninstallButton.HomePackages(homePackages, currentDefaultHome) } private fun signaturesMatch(packageName1: String, packageName2: String): Boolean = try { packageManager.checkSignatures(packageName1, packageName2) >= PackageManager.SIGNATURE_MATCH } catch (e: Exception) { // e.g. named alternate package not found during lookup; this is an expected case sometimes false } }
src/com/android/settings/spa/app/appsettings/AppButtons.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.content.pm.PackageInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spa.widget.button.ActionButtons import kotlinx.coroutines.flow.map @Composable fun AppButtons(packageInfoPresenter: PackageInfoPresenter) { val appButtonsHolder = remember { AppButtonsHolder(packageInfoPresenter) } appButtonsHolder.Dialogs() ActionButtons(actionButtons = appButtonsHolder.rememberActionsButtons().value) } private class AppButtonsHolder(private val packageInfoPresenter: PackageInfoPresenter) { private val appLaunchButton = AppLaunchButton(context = packageInfoPresenter.context) private val appDisableButton = AppDisableButton(packageInfoPresenter) private val appUninstallButton = AppUninstallButton(packageInfoPresenter) private val appForceStopButton = AppForceStopButton(packageInfoPresenter) @Composable fun rememberActionsButtons() = remember { packageInfoPresenter.flow.map { packageInfo -> if (packageInfo != null) getActionButtons(packageInfo) else emptyList() } }.collectAsState(initial = emptyList()) private fun getActionButtons(packageInfo: PackageInfo): List<ActionButton> = listOfNotNull( appLaunchButton.getActionButton(packageInfo), appDisableButton.getActionButton(packageInfo), appUninstallButton.getActionButton(packageInfo), appForceStopButton.getActionButton(packageInfo), ) @Composable fun Dialogs() { appDisableButton.DisableConfirmDialog() appForceStopButton.ForceStopConfirmDialog() } }
src/com/android/settings/spa/app/appsettings/AppDisableButton.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.app.admin.DevicePolicyManager import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.os.UserManager import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowCircleDown import androidx.compose.material.icons.outlined.HideSource import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.android.settings.R import com.android.settings.Utils import com.android.settings.overlay.FeatureFactory import com.android.settingslib.Utils as SettingsLibUtils import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spaprivileged.model.app.hasFlag import com.android.settingslib.spaprivileged.model.app.isDisabledUntilUsed class AppDisableButton( private val packageInfoPresenter: PackageInfoPresenter, ) { private val context = packageInfoPresenter.context private val appButtonRepository = AppButtonRepository(context) private val resources = context.resources private val packageManager = context.packageManager private val userManager = context.getSystemService(UserManager::class.java)!! private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!! private val applicationFeatureProvider = FeatureFactory.getFactory(context).getApplicationFeatureProvider(context) private var openConfirmDialog by mutableStateOf(false) fun getActionButton(packageInfo: PackageInfo): ActionButton? { val app = packageInfo.applicationInfo if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) return null return when { app.enabled && !app.isDisabledUntilUsed() -> { disableButton(enabled = isDisableButtonEnabled(packageInfo)) } else -> enableButton() } } /** * Gets whether a package can be disabled. */ private fun isDisableButtonEnabled(packageInfo: PackageInfo): Boolean { val packageName = packageInfo.packageName val app = packageInfo.applicationInfo return when { packageName in applicationFeatureProvider.keepEnabledPackages -> false // Home launcher apps need special handling. In system ones we don't risk downgrading // because that can interfere with home-key resolution. packageName in appButtonRepository.getHomePackageInfo().homePackages -> false // Try to prevent the user from bricking their phone by not allowing disabling of apps // signed with the system certificate. SettingsLibUtils.isSystemPackage(resources, packageManager, packageInfo) -> false // If this is a device admin, it can't be disabled. appButtonRepository.isActiveAdmin(app) -> false // We don't allow disabling DO/PO on *any* users if it's a system app, because // "disabling" is actually "downgrade to the system version + disable", and "downgrade" // will clear data on all users. Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, packageName) -> false appButtonRepository.isDisallowControl(app) -> false // system/vendor resource overlays can never be disabled. app.isResourceOverlay -> false else -> true } } private fun disableButton(enabled: Boolean) = ActionButton( text = context.getString(R.string.disable_text), imageVector = Icons.Outlined.HideSource, enabled = enabled, ) { openConfirmDialog = true } private fun enableButton() = ActionButton( text = context.getString(R.string.enable_text), imageVector = Icons.Outlined.ArrowCircleDown, ) { packageInfoPresenter.enable() } @Composable fun DisableConfirmDialog() { if (!openConfirmDialog) return AlertDialog( onDismissRequest = { openConfirmDialog = false }, confirmButton = { TextButton( onClick = { openConfirmDialog = false packageInfoPresenter.disable() }, ) { Text(stringResource(R.string.app_disable_dlg_positive)) } }, dismissButton = { TextButton(onClick = { openConfirmDialog = false }) { Text(stringResource(R.string.cancel)) } }, text = { Text(stringResource(R.string.app_disable_dlg_text)) }, ) } }
src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.app.settings.SettingsEnums import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.os.UserManager import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.android.settings.R import com.android.settingslib.RestrictedLockUtils import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin import com.android.settingslib.RestrictedLockUtilsInternal import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spaprivileged.model.app.hasFlag import com.android.settingslib.spaprivileged.model.app.userId class AppForceStopButton( private val packageInfoPresenter: PackageInfoPresenter, ) { private val context = packageInfoPresenter.context private val appButtonRepository = AppButtonRepository(context) private val packageManager = context.packageManager private var openConfirmDialog by mutableStateOf(false) fun getActionButton(packageInfo: PackageInfo): ActionButton { val app = packageInfo.applicationInfo return ActionButton( text = context.getString(R.string.force_stop), imageVector = Icons.Outlined.WarningAmber, enabled = isForceStopButtonEnable(app), ) { onForceStopButtonClicked(app) } } /** * Gets whether a package can be force stopped. */ private fun isForceStopButtonEnable(app: ApplicationInfo): Boolean = when { // User can't force stop device admin. appButtonRepository.isActiveAdmin(app) -> false appButtonRepository.isDisallowControl(app) -> false // If the app isn't explicitly stopped, then always show the force stop button. else -> !app.hasFlag(ApplicationInfo.FLAG_STOPPED) } private fun onForceStopButtonClicked(app: ApplicationInfo) { packageInfoPresenter.logAction(SettingsEnums.ACTION_APP_INFO_FORCE_STOP) getAdminRestriction(app)?.let { admin -> RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin) return } openConfirmDialog = true } private fun getAdminRestriction(app: ApplicationInfo): EnforcedAdmin? = when { packageManager.isPackageStateProtected(app.packageName, app.userId) -> { RestrictedLockUtilsInternal.getDeviceOwner(context) } else -> RestrictedLockUtilsInternal.checkIfRestrictionEnforced( context, UserManager.DISALLOW_APPS_CONTROL, app.userId ) } @Composable fun ForceStopConfirmDialog() { if (!openConfirmDialog) return AlertDialog( onDismissRequest = { openConfirmDialog = false }, confirmButton = { TextButton( onClick = { openConfirmDialog = false packageInfoPresenter.forceStop() }, ) { Text(stringResource(R.string.okay)) } }, dismissButton = { TextButton(onClick = { openConfirmDialog = false }) { Text(stringResource(R.string.cancel)) } }, title = { Text(stringResource(R.string.force_stop_dlg_title)) }, text = { Text(stringResource(R.string.force_stop_dlg_text)) }, ) } }
src/com/android/settings/spa/app/appsettings/AppLaunchButton.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appsettings import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Launch import com.android.settings.R import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spaprivileged.model.app.userHandle class AppLaunchButton(private val context: Context) { private val packageManager = context.packageManager fun getActionButton(packageInfo: PackageInfo): ActionButton? = packageManager.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent -> launchButton(intent, packageInfo.applicationInfo) } private fun launchButton(intent: Intent, app: ApplicationInfo) = ActionButton( text = context.getString(R.string.launch_instant_app), imageVector = Icons.Outlined.Launch, ) { context.startActivityAsUser(intent, app.userHandle) } }