Merge "Add uninstall updates & uninstall for all users"

This commit is contained in:
Chaohui Wang
2022-09-29 10:39:39 +00:00
committed by Android (Google) Code Review
7 changed files with 140 additions and 55 deletions

View File

@@ -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?,
)
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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)

View File

@@ -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) }

View File

@@ -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()
}
}

View File

@@ -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,17 +66,23 @@ 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) {
navController.navigateBack()
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()
}
}
}
}
@@ -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)