Merge "Update Special App Access Compose Components for ECM" into main
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
index 3acc9ad..b6d9242 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
@@ -18,6 +18,7 @@
 
 import android.app.admin.DevicePolicyResources.Strings.Settings
 import android.content.Context
+import android.content.Intent
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.widget.restricted.R
 
@@ -32,6 +33,11 @@
     fun sendShowAdminSupportDetailsIntent()
 }
 
+interface BlockedByEcm : RestrictedMode {
+    fun showRestrictedSettingsDetails()
+}
+
+
 internal data class BlockedByAdminImpl(
     private val context: Context,
     private val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin,
@@ -55,3 +61,13 @@
         RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, enforcedAdmin)
     }
 }
+
+internal data class BlockedByEcmImpl(
+    private val context: Context,
+    private val intent: Intent,
+) : BlockedByEcm {
+
+    override fun showRestrictedSettingsDetails() {
+        context.startActivity(intent)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 550966b..9432d59 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -29,10 +29,20 @@
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
 
+data class EnhancedConfirmation(
+    val key: String,
+    val uid: Int,
+    val packageName: String,
+)
 data class Restrictions(
     val userId: Int = UserHandle.myUserId(),
     val keys: List<String>,
-)
+    val enhancedConfirmation: EnhancedConfirmation? = null,
+) {
+    fun isEmpty(): Boolean {
+        return keys.isEmpty() && enhancedConfirmation == null
+    }
+}
 
 interface RestrictionsProvider {
     @Composable
@@ -77,6 +87,14 @@
                 .checkIfRestrictionEnforced(context, key, restrictions.userId)
                 ?.let { return BlockedByAdminImpl(context = context, enforcedAdmin = it) }
         }
+
+        restrictions.enhancedConfirmation?.let { ec ->
+            RestrictedLockUtilsInternal
+                    .checkIfRequiresEnhancedConfirmation(context, ec.key,
+                        ec.uid, ec.packageName)
+                    ?.let { intent -> return BlockedByEcmImpl(context = context, intent = intent) }
+        }
+
         return NoRestricted
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 06b3eab..25c3bc5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -47,6 +47,9 @@
     abstract val appOp: Int
     abstract val permission: String
 
+    override val enhancedConfirmationKey: String?
+        get() = AppOpsManager.opToPublicName(appOp)
+
     /**
      * When set, specifies the broader permission who trumps the [permission].
      *
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 5655436..1c830c1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -41,6 +41,7 @@
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
 import com.android.settingslib.spaprivileged.model.app.toRoute
+import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@@ -154,7 +155,12 @@
             override val changeable = { isChangeable }
             override val onCheckedChange: (Boolean) -> Unit = { setAllowed(record, it) }
         }
-        val restrictions = Restrictions(userId, switchRestrictionKeys)
+        val restrictions = Restrictions(userId = userId,
+            keys = switchRestrictionKeys,
+            enhancedConfirmation = enhancedConfirmationKey?.let { EnhancedConfirmation(
+                key = it,
+                uid = checkNotNull(applicationInfo).uid,
+                packageName = packageName) })
         RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 8704f20..916d83a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -36,6 +36,9 @@
     val switchRestrictionKeys: List<String>
         get() = emptyList()
 
+    val enhancedConfirmationKey: String?
+        get() = null
+
     /**
      * Loads the extra info for the App List, and generates the [AppRecord] List.
      *
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 36c91f4..4b47437 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -41,6 +41,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@@ -149,11 +150,17 @@
 
     @Composable
     fun getSummary(record: T): () -> String {
-        val restrictions = remember(record.app.userId) {
+        val restrictions = remember(record.app.userId,
+                record.app.uid, record.app.packageName) {
             Restrictions(
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
-            )
+                enhancedConfirmation = listModel.enhancedConfirmationKey?.let {
+                    EnhancedConfirmation(
+                        key = it,
+                        uid = record.app.uid,
+                        packageName = record.app.packageName)
+                })
         }
         val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions)
         val allowed = listModel.isAllowed(record)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index d5c5574..cd72025 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -40,7 +40,7 @@
     restrictions: Restrictions,
     restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
-    if (restrictions.keys.isEmpty()) {
+    if (restrictions.isEmpty()) {
         SwitchPreference(model)
         return
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index fa44ecb..aba3460 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -31,6 +31,7 @@
 import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
@@ -56,6 +57,7 @@
         is NoRestricted -> model.checked
         is BaseUserRestricted -> ({ false })
         is BlockedByAdmin -> model.checked
+        is BlockedByEcm -> model.checked
     }
 
     override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
@@ -68,24 +70,42 @@
         is BaseUserRestricted -> model.onCheckedChange
         // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
         is BlockedByAdmin -> null
+        is BlockedByEcm -> null
     }
 
     @Composable
     fun RestrictionWrapper(content: @Composable () -> Unit) {
-        if (restrictedMode !is BlockedByAdmin) {
-            content()
-            return
+        when (restrictedMode) {
+            is BlockedByAdmin -> {
+                Box(
+                    Modifier
+                            .clickable(
+                                role = Role.Switch,
+                                onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+                            )
+                            .semantics {
+                                this.toggleableState = ToggleableState(checked())
+                            },
+                ) { content() }
+            }
+
+            is BlockedByEcm -> {
+                Box(
+                    Modifier
+                            .clickable(
+                                role = Role.Switch,
+                                onClick = { restrictedMode.showRestrictedSettingsDetails() },
+                            )
+                            .semantics {
+                                this.toggleableState = ToggleableState(checked())
+                            },
+                ) { content() }
+            }
+
+            else -> {
+                content()
+            }
         }
-        Box(
-            Modifier
-                .clickable(
-                    role = Role.Switch,
-                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
-                )
-                .semantics {
-                    this.toggleableState = ToggleableState(checked())
-                },
-        ) { content() }
     }
 
     private fun ToggleableState(value: Boolean?) = when (value) {
@@ -123,6 +143,9 @@
                     context.getString(com.android.settingslib.R.string.disabled)
 
                 is BlockedByAdmin -> restrictedMode.getSummary(checked())
+                is BlockedByEcm ->
+                    context.getString(com.android.settingslib.R.string.disabled)
+
                 null -> context.getPlaceholder()
             }
         }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index f9abefc..977615b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -21,6 +21,7 @@
 import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@@ -47,6 +48,7 @@
     MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
         when (restrictedMode) {
             is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
+            is BlockedByEcm -> restrictedMode.showRestrictedSettingsDetails()
             else -> onClick()
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index e77964c..4454b71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -22,13 +22,16 @@
 
 import static com.android.settingslib.Utils.getColorAttrDefaultColor;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -39,10 +42,12 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManager.EnforcingUser;
+import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.ImageSpan;
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.MenuItem;
 import android.widget.TextView;
@@ -53,6 +58,7 @@
 import com.android.internal.widget.LockPatternUtils;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -62,6 +68,16 @@
 
     private static final String LOG_TAG = "RestrictedLockUtils";
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
+    private static final Set<String> ECM_KEYS = new ArraySet<>();
+
+    static {
+        if (android.security.Flags.extendEcmToAllSettings()) {
+            ECM_KEYS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
+            ECM_KEYS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
+            ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
+            ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN);
+        }
+    }
 
     /**
      * @return drawables for displaying with settings that are locked by a device admin.
@@ -81,6 +97,38 @@
     }
 
     /**
+     * Checks if a given permission requires additional confirmation for the given package
+     *
+     * @return An intent to show the user if additional confirmation is required, null otherwise
+     */
+    @Nullable
+    public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context,
+                                                             @NonNull String restriction,
+                                                             int uid,
+                                                             @Nullable String packageName) {
+        // TODO(b/297372999): Replace with call to mainline module once ready
+
+        if (!ECM_KEYS.contains(restriction)) {
+            return null;
+        }
+
+        final AppOpsManager appOps = (AppOpsManager) context
+                .getSystemService(Context.APP_OPS_SERVICE);
+        final int mode = appOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+                uid, packageName, null, null);
+        final boolean ecmEnabled = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+        if (ecmEnabled && mode != AppOpsManager.MODE_ALLOWED) {
+            final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+            intent.putExtra(Intent.EXTRA_UID, uid);
+            return intent;
+        }
+
+        return null;
+    }
+
+    /**
      * Checks if a restriction is enforced on a user and returns the enforced admin and
      * admin userId.
      *