Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9b65b158 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

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
parent 7075bc4f
Loading
Loading
Loading
Loading
+12 −18
Original line number Diff line number Diff line
@@ -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?,
    )
}
+7 −8
Original line number Diff line number Diff line
@@ -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"
+2 −1
Original line number Diff line number Diff line
@@ -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

+8 −3
Original line number Diff line number Diff line
@@ -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)
+85 −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.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) }
Loading