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

Commit ac67b77a authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

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

parents 853c4a86 90983daa
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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(
                KeyguardManager.class);

+75 −18
Original line number Diff line number Diff line
@@ -16,17 +16,25 @@

package com.android.settings.spa.app.appinfo

import android.app.AppOpsManager
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.UserManager
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.res.stringResource
import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
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.userManager
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.template.scaffold.RestrictedMenuItem
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext

@Composable
@@ -44,13 +54,11 @@ fun AppInfoSettingsMoreOptions(
    packageManagers: IPackageManagers = PackageManagers,
) {
    val state = app.produceState(packageManagers).value ?: return
    when {
        // 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.
        state.isProfileOrDeviceOwner -> return
        !state.shownUninstallUpdates && !state.shownUninstallForAllUsers -> return
    }
    var restrictedSettingsAllowed by rememberSaveable { mutableStateOf(false) }
    if (!state.shownUninstallUpdates &&
        !state.shownUninstallForAllUsers &&
        !(state.shouldShowAccessRestrictedSettings && !restrictedSettingsAllowed)
    ) return
    MoreOptionsAction {
        val restrictions =
            Restrictions(userId = app.userId, keys = listOf(UserManager.DISALLOW_APPS_CONTROL))
@@ -70,13 +78,37 @@ fun AppInfoSettingsMoreOptions(
                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(
    val isProfileOrDeviceOwner: Boolean,
    val shownUninstallUpdates: Boolean,
    val shownUninstallForAllUsers: Boolean,
    val shouldShowAccessRestrictedSettings: Boolean,
)

@Composable
@@ -86,18 +118,38 @@ private fun ApplicationInfo.produceState(
    val context = LocalContext.current
    return produceState<AppInfoSettingsMoreOptionsState?>(initialValue = null, this) {
        withContext(Dispatchers.IO) {
            value = AppInfoSettingsMoreOptionsState(
                isProfileOrDeviceOwner = Utils.isProfileOrDeviceOwner(
                    context.userManager, context.devicePolicyManager, packageName
                ),
                shownUninstallUpdates = isShowUninstallUpdates(context),
                shownUninstallForAllUsers = isShowUninstallForAllUsers(
            value = getMoreOptionsState(context, 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 =
@@ -116,3 +168,8 @@ private fun ApplicationInfo.isOtherUserHasInstallPackage(
): Boolean = userManager.aliveUsers
    .filter { it.id != userId }
    .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
+2 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@
          xmlns:tools="http://schemas.android.com/tools"
          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" />

    <application android:debuggable="true">
+43 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.settings.spa.app.appinfo

import android.app.AppOpsManager
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import android.content.Context
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.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
@@ -36,6 +39,7 @@ import com.android.settings.R
import com.android.settings.Utils
import com.android.settingslib.spa.testutils.delay
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.userManager
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
@@ -46,6 +50,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoSession
import org.mockito.Spy
import org.mockito.quality.Strictness
@@ -73,6 +78,12 @@ class AppInfoSettingsMoreOptionsTest {
    @Mock
    private lateinit var devicePolicyManager: DevicePolicyManager

    @Mock
    private lateinit var appOpsManager: AppOpsManager

    @Mock
    private lateinit var keyguardManager: KeyguardManager

    @Spy
    private var resources = context.resources

@@ -90,6 +101,9 @@ class AppInfoSettingsMoreOptionsTest {
        whenever(context.packageManager).thenReturn(packageManager)
        whenever(context.userManager).thenReturn(userManager)
        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))
            .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) {
        composeTestRule.setContent {
            CompositionLocalProvider(LocalContext provides context) {