Add uninstall updates & uninstall for all users
These are the items in the more options of App Settings page. Uninstall updates only shows up for updated system app. Uninstall for all users only shows up for primary user when a non-system app is installed on multiple users. Bug: 236346018 Test: Manual on App Settings page Change-Id: I7530ce5215ed921c0a2b767dce56cbfd9a2b0137
This commit is contained in:
@@ -17,19 +17,18 @@
|
||||
package com.android.settings.spa.app.appsettings
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.ComponentName
|
||||
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
|
||||
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
|
||||
import com.android.settingslib.spaprivileged.model.app.isDisallowControl
|
||||
|
||||
class AppButtonRepository(private val context: Context) {
|
||||
private val packageManager = context.packageManager
|
||||
private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!!
|
||||
private val devicePolicyManager = context.devicePolicyManager
|
||||
|
||||
/**
|
||||
* Checks whether the given application is disallowed from modifying.
|
||||
@@ -41,20 +40,10 @@ class AppButtonRepository(private val context: Context) {
|
||||
// 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
|
||||
else -> app.isDisallowControl(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given application is an active admin.
|
||||
*/
|
||||
fun isActiveAdmin(app: ApplicationInfo): Boolean =
|
||||
devicePolicyManager.packageHasActiveAdmins(app.packageName, app.userId)
|
||||
|
||||
fun getHomePackageInfo(): AppUninstallButton.HomePackages {
|
||||
fun getHomePackageInfo(): HomePackages {
|
||||
val homePackages = mutableSetOf<String>()
|
||||
val homeActivities = ArrayList<ResolveInfo>()
|
||||
val currentDefaultHome = packageManager.getHomeActivities(homeActivities)
|
||||
@@ -66,7 +55,7 @@ class AppButtonRepository(private val context: Context) {
|
||||
homePackages.add(metaPackageName)
|
||||
}
|
||||
}
|
||||
return AppUninstallButton.HomePackages(homePackages, currentDefaultHome)
|
||||
return HomePackages(homePackages, currentDefaultHome)
|
||||
}
|
||||
|
||||
private fun signaturesMatch(packageName1: String, packageName2: String): Boolean = try {
|
||||
@@ -75,4 +64,9 @@ class AppButtonRepository(private val context: Context) {
|
||||
// e.g. named alternate package not found during lookup; this is an expected case sometimes
|
||||
false
|
||||
}
|
||||
|
||||
data class HomePackages(
|
||||
val homePackages: Set<String>,
|
||||
val currentDefaultHome: ComponentName?,
|
||||
)
|
||||
}
|
||||
|
@@ -16,10 +16,7 @@
|
||||
|
||||
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
|
||||
@@ -34,10 +31,12 @@ 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.framework.common.devicePolicyManager
|
||||
import com.android.settingslib.spaprivileged.framework.common.userManager
|
||||
import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
|
||||
import com.android.settingslib.spaprivileged.model.app.isDisabledUntilUsed
|
||||
import com.android.settingslib.Utils as SettingsLibUtils
|
||||
|
||||
class AppDisableButton(
|
||||
private val packageInfoPresenter: PackageInfoPresenter,
|
||||
@@ -46,8 +45,8 @@ class AppDisableButton(
|
||||
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 userManager = context.userManager
|
||||
private val devicePolicyManager = context.devicePolicyManager
|
||||
private val applicationFeatureProvider =
|
||||
FeatureFactory.getFactory(context).getApplicationFeatureProvider(context)
|
||||
|
||||
@@ -84,7 +83,7 @@ class AppDisableButton(
|
||||
SettingsLibUtils.isSystemPackage(resources, packageManager, packageInfo) -> false
|
||||
|
||||
// If this is a device admin, it can't be disabled.
|
||||
appButtonRepository.isActiveAdmin(app) -> false
|
||||
app.isActiveAdmin(context) -> 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"
|
||||
|
@@ -36,6 +36,7 @@ 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.isActiveAdmin
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
|
||||
class AppForceStopButton(
|
||||
@@ -61,7 +62,7 @@ class AppForceStopButton(
|
||||
*/
|
||||
private fun isForceStopButtonEnable(app: ApplicationInfo): Boolean = when {
|
||||
// User can't force stop device admin.
|
||||
appButtonRepository.isActiveAdmin(app) -> false
|
||||
app.isActiveAdmin(context) -> false
|
||||
|
||||
appButtonRepository.isDisallowControl(app) -> false
|
||||
|
||||
|
@@ -60,7 +60,7 @@ object AppSettingsProvider : SettingsPageProvider {
|
||||
PackageInfoPresenter(context, packageName, userId, coroutineScope)
|
||||
}
|
||||
AppSettings(packageInfoPresenter)
|
||||
packageInfoPresenter.PageCloser()
|
||||
packageInfoPresenter.PackageRemoveDetector()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -77,7 +77,13 @@ object AppSettingsProvider : SettingsPageProvider {
|
||||
@Composable
|
||||
private fun AppSettings(packageInfoPresenter: PackageInfoPresenter) {
|
||||
val packageInfo = packageInfoPresenter.flow.collectAsState().value ?: return
|
||||
RegularScaffold(title = stringResource(R.string.application_info_label)) {
|
||||
val app = packageInfo.applicationInfo
|
||||
RegularScaffold(
|
||||
title = stringResource(R.string.application_info_label),
|
||||
actions = {
|
||||
AppSettingsMoreOptions(packageInfoPresenter, app)
|
||||
}
|
||||
) {
|
||||
val appInfoProvider = remember { AppInfoProvider(packageInfo) }
|
||||
|
||||
appInfoProvider.AppInfo()
|
||||
@@ -85,7 +91,6 @@ private fun AppSettings(packageInfoPresenter: PackageInfoPresenter) {
|
||||
AppButtons(packageInfoPresenter)
|
||||
|
||||
Category(title = stringResource(R.string.advanced_apps)) {
|
||||
val app = packageInfo.applicationInfo
|
||||
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
|
||||
ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
|
||||
PictureInPictureListProvider.InfoPageEntryItem(app)
|
||||
|
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.pm.ApplicationInfo
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.android.settings.R
|
||||
import com.android.settings.Utils
|
||||
import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
|
||||
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
|
||||
import com.android.settingslib.spaprivileged.framework.common.userManager
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers
|
||||
import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
|
||||
import com.android.settingslib.spaprivileged.model.app.isDisallowControl
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
|
||||
@Composable
|
||||
fun AppSettingsMoreOptions(packageInfoPresenter: PackageInfoPresenter, app: ApplicationInfo) {
|
||||
val context = LocalContext.current
|
||||
// We don't allow uninstalling update for DO/PO if it's a system app, because it will clear data
|
||||
// on all users. We also don't allow uninstalling for all users if it's DO/PO for any user.
|
||||
val isProfileOrDeviceOwner = remember(app) {
|
||||
Utils.isProfileOrDeviceOwner(
|
||||
context.userManager, context.devicePolicyManager, app.packageName
|
||||
)
|
||||
}
|
||||
if (isProfileOrDeviceOwner) return
|
||||
val shownUninstallUpdates = remember(app) { isShowUninstallUpdates(context, app) }
|
||||
val shownUninstallForAllUsers = remember(app) { isShowUninstallForAllUsers(context, app) }
|
||||
if (!shownUninstallUpdates && !shownUninstallForAllUsers) return
|
||||
MoreOptionsAction { onDismissRequest ->
|
||||
if (shownUninstallUpdates) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.app_factory_reset)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
packageInfoPresenter.startUninstallActivity(forAllUsers = false)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (shownUninstallForAllUsers) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.uninstall_all_users_text)) },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
packageInfoPresenter.startUninstallActivity(forAllUsers = true)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isShowUninstallUpdates(context: Context, app: ApplicationInfo): Boolean =
|
||||
app.isUpdatedSystemApp && context.userManager.isUserAdmin(app.userId) &&
|
||||
!app.isDisallowControl(context) &&
|
||||
!context.resources.getBoolean(R.bool.config_disable_uninstall_update)
|
||||
|
||||
private fun isShowUninstallForAllUsers(context: Context, app: ApplicationInfo): Boolean =
|
||||
app.userId == 0 && !app.isSystemApp && !app.isInstantApp && !app.isActiveAdmin(context) &&
|
||||
isOtherUserHasInstallPackage(context, app)
|
||||
|
||||
private fun isOtherUserHasInstallPackage(context: Context, app: ApplicationInfo): Boolean =
|
||||
context.userManager.aliveUsers
|
||||
.filter { it.id != app.userId }
|
||||
.any { PackageManagers.isPackageInstalledAsUser(app.packageName, it.id) }
|
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.settings.spa.app.appsettings
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
@@ -24,7 +23,6 @@ import android.content.om.OverlayManager
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.UserManager
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import com.android.settings.R
|
||||
@@ -33,16 +31,17 @@ import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAd
|
||||
import com.android.settingslib.RestrictedLockUtils
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal
|
||||
import com.android.settingslib.spa.widget.button.ActionButton
|
||||
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
|
||||
import com.android.settingslib.spaprivileged.model.app.hasFlag
|
||||
import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
|
||||
import com.android.settingslib.spaprivileged.model.app.userHandle
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
|
||||
class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter) {
|
||||
private val context = packageInfoPresenter.context
|
||||
private val appButtonRepository = AppButtonRepository(context)
|
||||
private val userManager = context.getSystemService(UserManager::class.java)!!
|
||||
private val overlayManager = context.getSystemService(OverlayManager::class.java)!!
|
||||
private val devicePolicyManager = context.getSystemService(DevicePolicyManager::class.java)!!
|
||||
private val devicePolicyManager = context.devicePolicyManager
|
||||
|
||||
fun getActionButton(packageInfo: PackageInfo): ActionButton? {
|
||||
val app = packageInfo.applicationInfo
|
||||
@@ -52,8 +51,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
||||
|
||||
/** Gets whether a package can be uninstalled. */
|
||||
private fun isUninstallButtonEnabled(app: ApplicationInfo): Boolean = when {
|
||||
// When we have multiple users, there is a separate menu to uninstall for all users.
|
||||
!app.hasFlag(ApplicationInfo.FLAG_INSTALLED) && userManager.users.size >= 2 -> false
|
||||
!app.hasFlag(ApplicationInfo.FLAG_INSTALLED) -> false
|
||||
|
||||
// Not allow to uninstall DO/PO.
|
||||
Utils.isProfileOrDeviceOwner(devicePolicyManager, app.packageName, app.userId) -> false
|
||||
@@ -101,7 +99,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
||||
) { onUninstallClicked(app) }
|
||||
|
||||
private fun onUninstallClicked(app: ApplicationInfo) {
|
||||
if (appButtonRepository.isActiveAdmin(app)) {
|
||||
if (app.isActiveAdmin(context)) {
|
||||
packageInfoPresenter.logAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN)
|
||||
val intent = Intent(context, DeviceAdminAdd::class.java).apply {
|
||||
putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, app.packageName)
|
||||
@@ -115,20 +113,6 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter)
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, admin)
|
||||
return
|
||||
}
|
||||
startUninstallActivity(app)
|
||||
}
|
||||
|
||||
data class HomePackages(
|
||||
val homePackages: Set<String>,
|
||||
val currentDefaultHome: ComponentName?,
|
||||
)
|
||||
|
||||
private fun startUninstallActivity(app: ApplicationInfo) {
|
||||
val packageUri = Uri.parse("package:${app.packageName}")
|
||||
packageInfoPresenter.logAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP)
|
||||
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri).apply {
|
||||
putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, !app.hasFlag(ApplicationInfo.FLAG_INSTALLED))
|
||||
}
|
||||
context.startActivityAsUser(intent, app.userHandle)
|
||||
packageInfoPresenter.startUninstallActivity()
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.UserHandle
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -65,20 +66,26 @@ class PackageInfoPresenter(
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the page when the package is uninstalled.
|
||||
* Detects the package removed event.
|
||||
*/
|
||||
@Composable
|
||||
fun PageCloser() {
|
||||
fun PackageRemoveDetector() {
|
||||
val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED).apply {
|
||||
addDataScheme("package")
|
||||
}
|
||||
val navController = LocalNavController.current
|
||||
DisposableBroadcastReceiverAsUser(userId, intentFilter) { intent ->
|
||||
if (packageName == intent.data?.schemeSpecificPart) {
|
||||
val packageInfo = flow.value
|
||||
if (packageInfo != null && packageInfo.applicationInfo.isSystemApp) {
|
||||
// System app still exists after uninstalling the updates, refresh the page.
|
||||
notifyChange()
|
||||
} else {
|
||||
navController.navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Enables this package. */
|
||||
fun enable() {
|
||||
@@ -102,6 +109,16 @@ class PackageInfoPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts the uninstallation activity. */
|
||||
fun startUninstallActivity(forAllUsers: Boolean = false) {
|
||||
logAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP)
|
||||
val packageUri = Uri.parse("package:${packageName}")
|
||||
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri).apply {
|
||||
putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, forAllUsers)
|
||||
}
|
||||
context.startActivityAsUser(intent, UserHandle.of(userId))
|
||||
}
|
||||
|
||||
/** Clears this instant app. */
|
||||
fun clearInstantApp() {
|
||||
logAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
|
||||
|
Reference in New Issue
Block a user