Merge "Fix fragment class for WIFI_SAVED_NETWORK_SETTINGS. Has been broken since the old access points implementation was deleted."
diff --git a/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt b/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt
index e1ee766..c5e84ae 100644
--- a/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppButtonRepository.kt
@@ -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 @@
// 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 @@
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 @@
// 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?,
+ )
}
diff --git a/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt b/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
index 365da7c..cc5a76e 100644
--- a/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppDisableButton.kt
@@ -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 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 @@
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 @@
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"
diff --git a/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt b/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt
index 4919163..c34eff0 100644
--- a/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppForceStopButton.kt
@@ -36,6 +36,7 @@
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 @@
*/
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
diff --git a/src/com/android/settings/spa/app/appsettings/AppSettings.kt b/src/com/android/settings/spa/app/appsettings/AppSettings.kt
index 4c960e5..615fa75 100644
--- a/src/com/android/settings/spa/app/appsettings/AppSettings.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppSettings.kt
@@ -60,7 +60,7 @@
PackageInfoPresenter(context, packageName, userId, coroutineScope)
}
AppSettings(packageInfoPresenter)
- packageInfoPresenter.PageCloser()
+ packageInfoPresenter.PackageRemoveDetector()
}
@Composable
@@ -77,7 +77,13 @@
@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 @@
AppButtons(packageInfoPresenter)
Category(title = stringResource(R.string.advanced_apps)) {
- val app = packageInfo.applicationInfo
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
PictureInPictureListProvider.InfoPageEntryItem(app)
diff --git a/src/com/android/settings/spa/app/appsettings/AppSettingsMoreOptions.kt b/src/com/android/settings/spa/app/appsettings/AppSettingsMoreOptions.kt
new file mode 100644
index 0000000..d17ff33
--- /dev/null
+++ b/src/com/android/settings/spa/app/appsettings/AppSettingsMoreOptions.kt
@@ -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) }
diff --git a/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt b/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt
index 3458f1d..cdb2cd0 100644
--- a/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt
+++ b/src/com/android/settings/spa/app/appsettings/AppUninstallButton.kt
@@ -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.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.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 @@
/** 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 @@
) { 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 @@
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()
}
}
diff --git a/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt
index b164d7c..299c0a7 100644
--- a/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appsettings/PackageInfoPresenter.kt
@@ -23,6 +23,7 @@
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 @@
}
/**
- * 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 @@
}
}
+ /** 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)