From 1b763b188d47fd56784c6d58880479ebb4590cc7 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 18 Nov 2022 13:44:44 +0800 Subject: [PATCH] Use isEssentialPackage instead of isSystemPackage This is faster, and we also no longer need to load the PackageInfos with the deprecated flag PackageManager.GET_SIGNATURES. Bug: 235727273 Test: Unit test Test: Manually with Settings App Change-Id: Ia09ed24ca2622a162ce6008fcd29a930812dbcc2 --- .../settings/spa/app/appinfo/AppButtons.kt | 18 +- .../spa/app/appinfo/AppClearButton.kt | 5 +- .../spa/app/appinfo/AppDisableButton.kt | 48 +++-- .../spa/app/appinfo/AppForceStopButton.kt | 4 +- .../spa/app/appinfo/AppInstallButton.kt | 4 +- .../spa/app/appinfo/AppLaunchButton.kt | 7 +- .../spa/app/appinfo/AppUninstallButton.kt | 4 +- .../spa/app/appinfo/AppDisableButtonTest.kt | 168 ++++++++++++++++++ 8 files changed, 207 insertions(+), 51 deletions(-) create mode 100644 tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt index c088fec69c5..06016fcb9c2 100644 --- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt +++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt @@ -16,7 +16,7 @@ package com.android.settings.spa.app.appinfo -import android.content.pm.PackageInfo +import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember @@ -58,17 +58,17 @@ private class AppButtonsPresenter(private val packageInfoPresenter: PackageInfoP @Composable fun rememberActionsButtons() = remember { packageInfoPresenter.flow.map { packageInfo -> - if (packageInfo != null) getActionButtons(packageInfo) else emptyList() + if (packageInfo != null) getActionButtons(packageInfo.applicationInfo) else emptyList() } }.collectAsState(initial = emptyList()) - private fun getActionButtons(packageInfo: PackageInfo): List = listOfNotNull( - appLaunchButton.getActionButton(packageInfo), - appInstallButton.getActionButton(packageInfo), - appDisableButton.getActionButton(packageInfo), - appUninstallButton.getActionButton(packageInfo), - appClearButton.getActionButton(packageInfo), - appForceStopButton.getActionButton(packageInfo), + private fun getActionButtons(app: ApplicationInfo): List = listOfNotNull( + appLaunchButton.getActionButton(app), + appInstallButton.getActionButton(app), + appDisableButton.getActionButton(app), + appUninstallButton.getActionButton(app), + appClearButton.getActionButton(app), + appForceStopButton.getActionButton(app), ) @Composable diff --git a/src/com/android/settings/spa/app/appinfo/AppClearButton.kt b/src/com/android/settings/spa/app/appinfo/AppClearButton.kt index c441071910d..ce00b73eed2 100644 --- a/src/com/android/settings/spa/app/appinfo/AppClearButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppClearButton.kt @@ -16,7 +16,7 @@ package com.android.settings.spa.app.appinfo -import android.content.pm.PackageInfo +import android.content.pm.ApplicationInfo import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.AlertDialog @@ -37,8 +37,7 @@ class AppClearButton( private var openConfirmDialog by mutableStateOf(false) - fun getActionButton(packageInfo: PackageInfo): ActionButton? { - val app = packageInfo.applicationInfo + fun getActionButton(app: ApplicationInfo): ActionButton? { if (!app.isInstantApp) return null return clearButton() diff --git a/src/com/android/settings/spa/app/appinfo/AppDisableButton.kt b/src/com/android/settings/spa/app/appinfo/AppDisableButton.kt index 79fb3866692..05b9706e7d4 100644 --- a/src/com/android/settings/spa/app/appinfo/AppDisableButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppDisableButton.kt @@ -17,7 +17,6 @@ package com.android.settings.spa.app.appinfo import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowCircleDown import androidx.compose.material.icons.outlined.HideSource @@ -52,13 +51,12 @@ class AppDisableButton( private var openConfirmDialog by mutableStateOf(false) - fun getActionButton(packageInfo: PackageInfo): ActionButton? { - val app = packageInfo.applicationInfo + fun getActionButton(app: ApplicationInfo): ActionButton? { if (!app.isSystemApp) return null return when { app.enabled && !app.isDisabledUntilUsed -> { - disableButton(app = app, enabled = isDisableButtonEnabled(packageInfo)) + disableButton(app) } else -> enableButton() @@ -68,38 +66,36 @@ class AppDisableButton( /** * 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 + private fun ApplicationInfo.canBeDisabled(): Boolean = when { + // Try to prevent the user from bricking their phone by not allowing disabling of apps + // signed with the system certificate. + isSignedWithPlatformKey -> 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 + // system/vendor resource overlays can never be disabled. + isResourceOverlay -> 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 + packageName in applicationFeatureProvider.keepEnabledPackages -> 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 + // 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 - appButtonRepository.isDisallowControl(app) -> false + SettingsLibUtils.isEssentialPackage(resources, packageManager, packageName) -> false - // system/vendor resource overlays can never be disabled. - app.isResourceOverlay -> 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 - else -> true - } + appButtonRepository.isDisallowControl(this) -> false + + else -> true } - private fun disableButton(app: ApplicationInfo, enabled: Boolean) = ActionButton( + private fun disableButton(app: ApplicationInfo) = ActionButton( text = context.getString(R.string.disable_text), imageVector = Icons.Outlined.HideSource, - enabled = enabled, + enabled = app.canBeDisabled(), ) { // Currently we apply the same device policy for both the uninstallation and disable button. if (!appButtonRepository.isUninstallBlockedByAdmin(app)) { diff --git a/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt b/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt index 52ce3dfb5f6..d05c8321074 100644 --- a/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt @@ -18,7 +18,6 @@ package com.android.settings.spa.app.appinfo 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 @@ -48,8 +47,7 @@ class AppForceStopButton( private var openConfirmDialog by mutableStateOf(false) - fun getActionButton(packageInfo: PackageInfo): ActionButton { - val app = packageInfo.applicationInfo + fun getActionButton(app: ApplicationInfo): ActionButton { return ActionButton( text = context.getString(R.string.force_stop), imageVector = Icons.Outlined.WarningAmber, diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt b/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt index 4ff246115c5..caf6a4d4350 100644 --- a/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt @@ -18,7 +18,6 @@ package com.android.settings.spa.app.appinfo 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.FileDownload import com.android.settings.R @@ -29,8 +28,7 @@ import com.android.settingslib.spaprivileged.model.app.userHandle class AppInstallButton(private val packageInfoPresenter: PackageInfoPresenter) { private val context = packageInfoPresenter.context - fun getActionButton(packageInfo: PackageInfo): ActionButton? { - val app = packageInfo.applicationInfo + fun getActionButton(app: ApplicationInfo): ActionButton? { if (!app.isInstantApp) return null return AppStoreUtil.getAppStoreLink(packageInfoPresenter.userContext, app.packageName) diff --git a/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt b/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt index 9e82f53e146..a1c3273eae3 100644 --- a/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt @@ -18,7 +18,6 @@ package com.android.settings.spa.app.appinfo 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 @@ -29,9 +28,9 @@ class AppLaunchButton(packageInfoPresenter: PackageInfoPresenter) { private val context = packageInfoPresenter.context private val userPackageManager = packageInfoPresenter.userPackageManager - fun getActionButton(packageInfo: PackageInfo): ActionButton? = - userPackageManager.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent -> - launchButton(intent, packageInfo.applicationInfo) + fun getActionButton(app: ApplicationInfo): ActionButton? = + userPackageManager.getLaunchIntentForPackage(app.packageName)?.let { intent -> + launchButton(intent, app) } private fun launchButton(intent: Intent, app: ApplicationInfo) = ActionButton( diff --git a/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt b/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt index 4b95f7bebde..f05d611984f 100644 --- a/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppUninstallButton.kt @@ -18,7 +18,6 @@ package com.android.settings.spa.app.appinfo import android.content.om.OverlayManager import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete import com.android.settings.R @@ -32,8 +31,7 @@ class AppUninstallButton(private val packageInfoPresenter: PackageInfoPresenter) private val appButtonRepository = AppButtonRepository(context) private val overlayManager = context.getSystemService(OverlayManager::class.java)!! - fun getActionButton(packageInfo: PackageInfo): ActionButton? { - val app = packageInfo.applicationInfo + fun getActionButton(app: ApplicationInfo): ActionButton? { if (app.isSystemApp || app.isInstantApp) return null return uninstallButton(app = app, enabled = isUninstallButtonEnabled(app)) } diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt new file mode 100644 index 00000000000..f35810ff858 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDisableButtonTest.kt @@ -0,0 +1,168 @@ +/* + * 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.appinfo + +import android.app.admin.DevicePolicyManager +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.os.UserManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.settings.Utils +import com.android.settings.testutils.FakeFeatureFactory +import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager +import com.android.settingslib.spaprivileged.framework.common.userManager +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoSession +import org.mockito.Spy +import org.mockito.quality.Strictness +import com.android.settingslib.Utils as SettingsLibUtils +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidJUnit4::class) +class AppDisableButtonTest { + + private lateinit var mockSession: MockitoSession + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var packageInfoPresenter: PackageInfoPresenter + + @Mock + private lateinit var packageManager: PackageManager + + @Mock + private lateinit var userManager: UserManager + + @Mock + private lateinit var devicePolicyManager: DevicePolicyManager + + private val fakeFeatureFactory = FakeFeatureFactory() + private val appFeatureProvider = fakeFeatureFactory.applicationFeatureProvider + + private lateinit var appDisableButton: AppDisableButton + + @Before + fun setUp() { + mockSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .mockStatic(SettingsLibUtils::class.java) + .mockStatic(Utils::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + whenever(packageInfoPresenter.context).thenReturn(context) + whenever(context.packageManager).thenReturn(packageManager) + whenever(context.userManager).thenReturn(userManager) + whenever(context.devicePolicyManager).thenReturn(devicePolicyManager) + whenever(appFeatureProvider.keepEnabledPackages).thenReturn(emptySet()) + whenever( + SettingsLibUtils.isEssentialPackage(context.resources, packageManager, PACKAGE_NAME) + ).thenReturn(false) + whenever(Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, PACKAGE_NAME)) + .thenReturn(false) + appDisableButton = AppDisableButton(packageInfoPresenter) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun getActionButton_signedWithPlatformKey_cannotDisable() { + val app = enabledSystemApp { + privateFlags = privateFlags or ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY + } + + val actionButton = appDisableButton.getActionButton(app)!! + + assertThat(actionButton.enabled).isFalse() + } + + @Test + fun getActionButton_isResourceOverlay_cannotDisable() { + val app = enabledSystemApp { + privateFlags = privateFlags or ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY + } + + val actionButton = appDisableButton.getActionButton(app)!! + + assertThat(actionButton.enabled).isFalse() + } + + @Test + fun getActionButton_isKeepEnabledPackages_cannotDisable() { + whenever(appFeatureProvider.keepEnabledPackages).thenReturn(setOf(PACKAGE_NAME)) + val app = enabledSystemApp() + + val actionButton = appDisableButton.getActionButton(app)!! + + assertThat(actionButton.enabled).isFalse() + } + + @Test + fun getActionButton_isEssentialPackage_cannotDisable() { + whenever( + SettingsLibUtils.isEssentialPackage(context.resources, packageManager, PACKAGE_NAME) + ).thenReturn(true) + val app = enabledSystemApp() + + val actionButton = appDisableButton.getActionButton(app)!! + + assertThat(actionButton.enabled).isFalse() + } + + @Test + fun getActionButton_isProfileOrDeviceOwner_cannotDisable() { + whenever(Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, PACKAGE_NAME)) + .thenReturn(true) + val app = enabledSystemApp() + + val actionButton = appDisableButton.getActionButton(app)!! + + assertThat(actionButton.enabled).isFalse() + } + + @Test + fun getActionButton_regularEnabledSystemApp_canDisable() { + val app = enabledSystemApp() + + val actionButton = appDisableButton.getActionButton(app)!! + + assertThat(actionButton.enabled).isTrue() + } + + private fun enabledSystemApp(builder: ApplicationInfo.() -> Unit = {}) = + ApplicationInfo().apply { + packageName = PACKAGE_NAME + enabled = true + flags = ApplicationInfo.FLAG_SYSTEM + }.apply(builder) + + private companion object { + const val PACKAGE_NAME = "package.name" + } +}