Merge "Fix not displaying "Allow restricted settings"" into udc-dev am: ac67b77af7

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/22135265

Change-Id: I966c86b96402de9dfea45586d884578d0e8f0cf3
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chaohui Wang
2023-03-20 16:43:42 +00:00
committed by Automerger Merge Worker
4 changed files with 122 additions and 19 deletions

View File

@@ -437,7 +437,8 @@ public class AppInfoDashboardFragment extends DashboardFragment
} }
} }
private static void showLockScreen(Context context, Runnable successRunnable) { /** Shows the lock screen if the keyguard is secured. */
public static void showLockScreen(Context context, Runnable successRunnable) {
final KeyguardManager keyguardManager = context.getSystemService( final KeyguardManager keyguardManager = context.getSystemService(
KeyguardManager.class); KeyguardManager.class);

View File

@@ -16,17 +16,25 @@
package com.android.settings.spa.app.appinfo package com.android.settings.spa.app.appinfo
import android.app.AppOpsManager
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.os.UserManager import android.os.UserManager
import android.widget.Toast
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.android.settings.R import com.android.settings.R
import com.android.settings.Utils import com.android.settings.Utils
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
import com.android.settingslib.spaprivileged.framework.common.userManager import com.android.settingslib.spaprivileged.framework.common.userManager
import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.IPackageManagers
@@ -35,6 +43,8 @@ import com.android.settingslib.spaprivileged.model.app.userId
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.template.scaffold.RestrictedMenuItem import com.android.settingslib.spaprivileged.template.scaffold.RestrictedMenuItem
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
@@ -44,13 +54,11 @@ fun AppInfoSettingsMoreOptions(
packageManagers: IPackageManagers = PackageManagers, packageManagers: IPackageManagers = PackageManagers,
) { ) {
val state = app.produceState(packageManagers).value ?: return val state = app.produceState(packageManagers).value ?: return
when { var restrictedSettingsAllowed by rememberSaveable { mutableStateOf(false) }
// We don't allow uninstalling update for DO/PO if it's a system app, because it will clear if (!state.shownUninstallUpdates &&
// data on all users. We also don't allow uninstalling for all users if it's DO/PO for any !state.shownUninstallForAllUsers &&
// user. !(state.shouldShowAccessRestrictedSettings && !restrictedSettingsAllowed)
state.isProfileOrDeviceOwner -> return ) return
!state.shownUninstallUpdates && !state.shownUninstallForAllUsers -> return
}
MoreOptionsAction { MoreOptionsAction {
val restrictions = val restrictions =
Restrictions(userId = app.userId, keys = listOf(UserManager.DISALLOW_APPS_CONTROL)) Restrictions(userId = app.userId, keys = listOf(UserManager.DISALLOW_APPS_CONTROL))
@@ -70,13 +78,37 @@ fun AppInfoSettingsMoreOptions(
packageInfoPresenter.startUninstallActivity(forAllUsers = true) packageInfoPresenter.startUninstallActivity(forAllUsers = true)
} }
} }
if (state.shouldShowAccessRestrictedSettings && !restrictedSettingsAllowed) {
MenuItem(text = stringResource(R.string.app_restricted_settings_lockscreen_title)) {
app.allowRestrictedSettings(packageInfoPresenter.context) {
restrictedSettingsAllowed = true
}
}
}
}
}
private fun ApplicationInfo.allowRestrictedSettings(context: Context, onSuccess: () -> Unit) {
AppInfoDashboardFragment.showLockScreen(context) {
context.appOpsManager.setMode(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
uid,
packageName,
AppOpsManager.MODE_ALLOWED,
)
onSuccess()
val toastString = context.getString(
R.string.toast_allows_restricted_settings_successfully,
loadLabel(context.packageManager),
)
Toast.makeText(context, toastString, Toast.LENGTH_LONG).show()
} }
} }
private data class AppInfoSettingsMoreOptionsState( private data class AppInfoSettingsMoreOptionsState(
val isProfileOrDeviceOwner: Boolean,
val shownUninstallUpdates: Boolean, val shownUninstallUpdates: Boolean,
val shownUninstallForAllUsers: Boolean, val shownUninstallForAllUsers: Boolean,
val shouldShowAccessRestrictedSettings: Boolean,
) )
@Composable @Composable
@@ -86,20 +118,40 @@ private fun ApplicationInfo.produceState(
val context = LocalContext.current val context = LocalContext.current
return produceState<AppInfoSettingsMoreOptionsState?>(initialValue = null, this) { return produceState<AppInfoSettingsMoreOptionsState?>(initialValue = null, this) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
value = AppInfoSettingsMoreOptionsState( value = getMoreOptionsState(context, packageManagers)
isProfileOrDeviceOwner = Utils.isProfileOrDeviceOwner(
context.userManager, context.devicePolicyManager, packageName
),
shownUninstallUpdates = isShowUninstallUpdates(context),
shownUninstallForAllUsers = isShowUninstallForAllUsers(
userManager = context.userManager,
packageManagers = packageManagers,
),
)
} }
} }
} }
private suspend fun ApplicationInfo.getMoreOptionsState(
context: Context,
packageManagers: IPackageManagers,
) = coroutineScope {
val shownUninstallUpdatesDeferred = async {
isShowUninstallUpdates(context)
}
val shownUninstallForAllUsersDeferred = async {
isShowUninstallForAllUsers(
userManager = context.userManager,
packageManagers = packageManagers,
)
}
val shouldShowAccessRestrictedSettingsDeferred = async {
shouldShowAccessRestrictedSettings(context.appOpsManager)
}
val isProfileOrDeviceOwner =
Utils.isProfileOrDeviceOwner(context.userManager, context.devicePolicyManager, packageName)
AppInfoSettingsMoreOptionsState(
// We don't allow uninstalling update for DO/PO if it's a system app, because it will clear
// data on all users.
shownUninstallUpdates = !isProfileOrDeviceOwner && shownUninstallUpdatesDeferred.await(),
// We also don't allow uninstalling for all users if it's DO/PO for any user.
shownUninstallForAllUsers =
!isProfileOrDeviceOwner && shownUninstallForAllUsersDeferred.await(),
shouldShowAccessRestrictedSettings = shouldShowAccessRestrictedSettingsDeferred.await(),
)
}
private fun ApplicationInfo.isShowUninstallUpdates(context: Context): Boolean = private fun ApplicationInfo.isShowUninstallUpdates(context: Context): Boolean =
isUpdatedSystemApp && context.userManager.isUserAdmin(userId) && isUpdatedSystemApp && context.userManager.isUserAdmin(userId) &&
!context.resources.getBoolean(R.bool.config_disable_uninstall_update) !context.resources.getBoolean(R.bool.config_disable_uninstall_update)
@@ -116,3 +168,8 @@ private fun ApplicationInfo.isOtherUserHasInstallPackage(
): Boolean = userManager.aliveUsers ): Boolean = userManager.aliveUsers
.filter { it.id != userId } .filter { it.id != userId }
.any { packageManagers.isPackageInstalledAsUser(packageName, it.id) } .any { packageManagers.isPackageInstalledAsUser(packageName, it.id) }
private fun ApplicationInfo.shouldShowAccessRestrictedSettings(appOpsManager: AppOpsManager) =
appOpsManager.noteOpNoThrow(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, uid, packageName, null, null
) == AppOpsManager.MODE_IGNORED

View File

@@ -19,6 +19,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.android.settings.tests.spa_unit"> package="com.android.settings.tests.spa_unit">
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
<application android:debuggable="true"> <application android:debuggable="true">

View File

@@ -16,6 +16,8 @@
package com.android.settings.spa.app.appinfo package com.android.settings.spa.app.appinfo
import android.app.AppOpsManager
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
@@ -27,6 +29,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.hasText import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
@@ -36,6 +39,7 @@ import com.android.settings.R
import com.android.settings.Utils import com.android.settings.Utils
import com.android.settingslib.spa.testutils.delay import com.android.settingslib.spa.testutils.delay
import com.android.settingslib.spa.testutils.waitUntilExists import com.android.settingslib.spa.testutils.waitUntilExists
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
import com.android.settingslib.spaprivileged.framework.common.userManager import com.android.settingslib.spaprivileged.framework.common.userManager
import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.IPackageManagers
@@ -46,6 +50,7 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoSession import org.mockito.MockitoSession
import org.mockito.Spy import org.mockito.Spy
import org.mockito.quality.Strictness import org.mockito.quality.Strictness
@@ -73,6 +78,12 @@ class AppInfoSettingsMoreOptionsTest {
@Mock @Mock
private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var devicePolicyManager: DevicePolicyManager
@Mock
private lateinit var appOpsManager: AppOpsManager
@Mock
private lateinit var keyguardManager: KeyguardManager
@Spy @Spy
private var resources = context.resources private var resources = context.resources
@@ -90,6 +101,9 @@ class AppInfoSettingsMoreOptionsTest {
whenever(context.packageManager).thenReturn(packageManager) whenever(context.packageManager).thenReturn(packageManager)
whenever(context.userManager).thenReturn(userManager) whenever(context.userManager).thenReturn(userManager)
whenever(context.devicePolicyManager).thenReturn(devicePolicyManager) whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
whenever(context.appOpsManager).thenReturn(appOpsManager)
whenever(context.getSystemService(KeyguardManager::class.java)).thenReturn(keyguardManager)
whenever(keyguardManager.isKeyguardSecure).thenReturn(false)
whenever(Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, PACKAGE_NAME)) whenever(Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, PACKAGE_NAME))
.thenReturn(false) .thenReturn(false)
} }
@@ -143,6 +157,35 @@ class AppInfoSettingsMoreOptionsTest {
) )
} }
@Test
fun shouldShowAccessRestrictedSettings() {
whenever(
appOpsManager.noteOpNoThrow(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, UID, PACKAGE_NAME, null, null
)
).thenReturn(AppOpsManager.MODE_IGNORED)
val app = ApplicationInfo().apply {
packageName = PACKAGE_NAME
uid = UID
}
setContent(app)
composeTestRule.onRoot().performClick()
composeTestRule.waitUntilExists(
hasText(context.getString(R.string.app_restricted_settings_lockscreen_title))
)
composeTestRule
.onNodeWithText(context.getString(R.string.app_restricted_settings_lockscreen_title))
.performClick()
verify(appOpsManager).setMode(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
UID,
PACKAGE_NAME,
AppOpsManager.MODE_ALLOWED,
)
}
private fun setContent(app: ApplicationInfo) { private fun setContent(app: ApplicationInfo) {
composeTestRule.setContent { composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { CompositionLocalProvider(LocalContext provides context) {