Fix not displaying "Allow restricted settings"
"Allow restricted settings" is missed from SPA, added to SPA to fix this issue. Also make the system call in app info more options async to improve performance. Fix: 273678047 Test: Unit test Test: By the following steps, 1. Install an app with accessibility feature from Chrome 2. Go Accessibility page and click on the disabled grey app 3. Go to the app info page, click more options 4. Make sure "Allow restricted settings" is displayed Change-Id: I4adbe2335a32e6a7c4ebe155715684d768e5d1ef
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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">
|
||||||
|
@@ -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) {
|
||||||
|
Reference in New Issue
Block a user