summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jay Sullivan <jaysullivan@google.com> 2024-01-17 05:09:49 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-01-17 05:09:49 +0000
commit23ebde4a4b41845e23041befd0258b0be26811f1 (patch)
tree148569ef7e4904d312912e4eaaac050a735ef6ed
parentb58f90b10ec5e40025b24f4a89d09c0d748344fe (diff)
parent8a616a4b56d0fb37a4b9e15969e008067c654dde (diff)
Merge "[ECM] Implement EnhancedConfirmationManager" into main
-rw-r--r--Android.bp1
-rw-r--r--framework-s/Android.bp1
-rw-r--r--framework-s/api/module-lib-current.txt8
-rw-r--r--framework-s/api/system-current.txt11
-rw-r--r--framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java50
-rw-r--r--framework-s/java/android/app/ecm/EnhancedConfirmationManager.java411
6 files changed, 482 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
index 59742acc1..389f330a6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -107,6 +107,7 @@ bootclasspath_fragment {
// result in a build failure due to inconsistent flags.
package_prefixes: [
"android.app.role",
+ "android.app.ecm",
"android.permission.jarjar",
"android.safetycenter",
"android.safetylabel",
diff --git a/framework-s/Android.bp b/framework-s/Android.bp
index 7c958f923..961ae4425 100644
--- a/framework-s/Android.bp
+++ b/framework-s/Android.bp
@@ -100,6 +100,7 @@ java_sdk_library {
permitted_packages: [
"android.permission",
"android.app.role",
+ "android.app.ecm",
"android.safetycenter",
"android.safetylabel",
],
diff --git a/framework-s/api/module-lib-current.txt b/framework-s/api/module-lib-current.txt
index 80f1cde45..14f71782e 100644
--- a/framework-s/api/module-lib-current.txt
+++ b/framework-s/api/module-lib-current.txt
@@ -1,4 +1,12 @@
// Signature format: 2.0
+package android.app.ecm {
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public class EnhancedConfirmationFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ }
+
+}
+
package android.app.role {
public class RoleFrameworkInitializer {
diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt
index b5c379937..e8d1b8992 100644
--- a/framework-s/api/system-current.txt
+++ b/framework-s/api/system-current.txt
@@ -1,4 +1,15 @@
// Signature format: 2.0
+package android.app.ecm {
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public final class EnhancedConfirmationManager {
+ method public void clearRestriction(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.app.PendingIntent getRestrictedSettingDialogIntent(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public boolean isClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public boolean isRestricted(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ }
+
+}
+
package android.app.role {
public interface OnRoleHoldersChangedListener {
diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java b/framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java
new file mode 100644
index 000000000..b4c18573a
--- /dev/null
+++ b/framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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 android.app.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.annotation.TargetApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+import android.os.Build;
+import android.permission.flags.Flags;
+
+/**
+ * Class holding initialization code for enhanced confirmation code in the permission module.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class EnhancedConfirmationFrameworkInitializer {
+ private EnhancedConfirmationFrameworkInitializer() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers
+ * {@link EnhancedConfirmationManager} to {@link Context}, so that
+ * {@link Context#getSystemService} can return it.
+ *
+ * <p>If this is called from other places, it throws a {@link IllegalStateException}.
+ */
+ public static void registerServiceWrappers() {
+ SystemServiceRegistry.registerContextAwareService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE,
+ EnhancedConfirmationManager.class,
+ (context) -> new EnhancedConfirmationManager(context));
+ }
+}
diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
new file mode 100644
index 000000000..23dc10c71
--- /dev/null
+++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2023 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 android.app.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TargetApi;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
+import android.os.UserHandle;
+import android.permission.flags.Flags;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+
+/**
+ * This class provides the core API for ECM (Enhanced Confirmation Mode). ECM is a feature that
+ * restricts access to protected **settings** (i.e., sensitive resources) by restricted **apps**
+ * (apps from from dangerous sources, such as sideloaded packages or packages downloaded from a web
+ * browser).
+ *
+ * <p>Specifically, this class provides the ability to:
+ *
+ * <ol>
+ * <li>Check whether a setting is restricted from an app ({@link #isRestricted})
+ * <li>Get an intent that will open the "Restricted setting" dialog ({@link
+ * #getRestrictedSettingDialogIntent}) (a dialog that informs the user that the operation
+ * they've attempted to perform is restricted)
+ * <li>Check whether an app is eligible to have its restriction status cleared ({@link
+ * #isClearRestrictionAllowed})
+ * <li>Clear an app's restriction status (i.e., un-restrict it). ({@link #clearRestriction})
+ * </ol>
+ *
+ * <p>Methods of this class will generally accept an app (identified by a packageName and a user)
+ * and a "setting" (a string representing the "sensitive resource") as arguments. ECM's exact
+ * behavior will generally depend on what restriction state ECM considers each setting and app. For
+ * example:
+ *
+ * <ol>
+ * <li>A setting may be considered by ECM to be either **protected** or **not protected**. In
+ * general, this should be considered hardcoded into ECM's implementation: nothing can
+ * "protect" or "unprotect" a setting.
+ * <li>An app may be considered as being **not restricted** or **restricted**. A restricted app
+ * will be restricted from accessing all protected settings. Whether ECM considers any
+ * particular app restricted is an implementation detail of ECM. However, the user is able to
+ * clear any restricted app's restriction status (i.e, un-restrict it), after which ECM will
+ * consider the app **not restricted**.
+ * </ol>
+ *
+ * Why is ECM needed? Consider the following (pre-ECM) scenario:
+ *
+ * <ol>
+ * <li>The user downloads and installs an apk file from a browser.
+ * <li>The user opens Settings -> Accessibility
+ * <li>The user tries to register the app as an accessibility service.
+ * <li>The user is shown a permission prompt "Allow _ to have full control of your device?"
+ * <li>The user clicks "Allow"
+ * <li>The downloaded app now has full control of the device.
+ * </ol>
+ *
+ * The purpose of ECM is to add more friction to this scenario.
+ *
+ * <p>With ECM, this scenario becomes:
+ *
+ * <ol>
+ * <li>The user downloads and installs an apk file from a browser.
+ * <li>The user goes into Settings -> Accessibility.
+ * <li>The user tries to register the app as an accessibility service.
+ * <li>The user is presented with a "Restricted setting" dialog explaining that the attempted
+ * action has been restricted. (No "allow" button is shown, but a link is given to a screen
+ * with intentionally-obscure instructions on how to proceed.)
+ * <li>The user must now navigate to Settings -> Apps -> [app]
+ * <li>The user then must click on "..." (top-right corner hamburger menu), then click "Allow
+ * restricted settings"
+ * <li>The user goes (again) into Settings -> Accessibility and (again) tries to register the app
+ * as an accessibility service.
+ * <li>The user is shown a permission prompt "Allow _ to have full control of your device?"
+ * <li>The user clicks "Allow"
+ * <li>The downloaded app now has full control of the device.
+ * </ol>
+ *
+ * And, expanding on the above scenario, the role that this class plays is as follows:
+ *
+ * <ol>
+ * <li>The user downloads and installs an apk file from a browser.
+ * <li>The user goes into Settings -> Accessibility.
+ * <p>**This screen then calls {@link #isRestricted}, which checks whether each app listed
+ * on-screen is restricted from the accessibility service setting. It uses this to visually
+ * "gray out" restricted apps.**
+ * <li>The user tries to register the app as an accessibility service.
+ * <p>**This screen then calls {@link #getRestrictedSettingDialogIntent} and starts the
+ * intent. This opens the "Restricted setting" dialog.**
+ * <li>The user is presented with a "Restricted setting" dialog explaining that the attempted
+ * action is restricted. (No "allow" button is shown, but a link is given to a screen with
+ * intentionally-obscure instructions on how to proceed.)
+ * <p>**Upon opening, this dialog marks the app as eligible to have its restriction status
+ * cleared.**
+ * <li>The user must now navigate to Settings -> Apps -> [app].
+ * <p>**This screen calls {@link #isClearRestrictionAllowed} to check whether the app is
+ * eligible to have its restriction status cleared. If this returns {@code true}, this screen
+ * should then show a "Allow restricted setting" button inside the top-right hamburger menu.**
+ * <li>The user then must click on "..." (top-right corner hamburger menu), then click "Allow
+ * restricted settings".
+ * <p>**In response, this screen should now call {@link #clearRestriction}.**
+ * <li>The user goes (again) into Settings -> Accessibility and (again) tries to register the app
+ * as an accessibility service.
+ * <li>The user is shown a permission prompt "Allow _ to have full control of your device?"
+ * <li>The user clicks "Allow"
+ * <li>The downloaded app now has full control of the device.
+ * </ol>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SystemService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE)
+public final class EnhancedConfirmationManager {
+ /*
+ * At the API level, we use the following terminology:
+ *
+ * - The capability of an app to access a setting may be considered (by ECM) to be *restricted*
+ * or *not restricted*.
+ * - A setting may be considered (by ECM) to be *protected* or *not protected*.
+ * - The state of an app may be considered (by ECM) to be *restricted* or *not restricted*
+ *
+ * In this implementation, however, the state of an app is considered either **guarded** or
+ * **not guarded**; these terms can generally be considered synonymous with **restricted** and
+ * **not restricted**. (Keeping in mind that, the capability of any app to access any
+ * non-protected setting will always be considered "not restricted", even if the state of the
+ * app is considered "restricted".). An app can also be in a third state: **guarded and
+ * acknowledged**, which corresponds with an app that is restricted and is eligible to have its
+ * restriction status cleared.
+ *
+ * Currently, the ECM state of any given app is stored in the OP_ACCESS_RESTRICTED_SETTINGS
+ * appop (though this may change in the future):
+ *
+ * - MODE_ALLOWED means the app is explicitly **not guarded**. (U- default)
+ * - MODE_ERRORED means the app is explicitly **guarded**. (Only settable in U-.)
+ * - MODE_IGNORED means the app is explicitly **guarded and acknowledged**. (An app enters this
+ * state as soon as the "Restricted setting" dialog has been shown to the user. If an app is
+ * in this state, Settings is now allowed to provide the user with the option to clear the
+ * restriction.)
+ * - MODE_DEFAULT means the app's ECM state should be decided lazily. (V+ default) (That is,
+ * each time a caller checks whether or not an app is considered guarded by ECM, we'll run an
+ * heuristic to determine this.)
+ *
+ * Some notes on compatibility:
+ *
+ * - On U-, MODE_ALLOWED is the default mode of OP_ACCESS_RESTRICTED_SETTINGS. On both U- and
+ * V+, this is also the mode after the app's restriction has been cleared.
+ * - In U-, the mode needed to be explicitly set (for example, by a browser that allows a
+ * dangerous app to be installed) to MODE_ERRORED to indicate that an app is guarded. In V+,
+ * we no longer allow an app to be placed into MODE_ERRORED, but for compatibility, we still
+ * recognize MODE_ERRORED to indicate that an app is explicitly guarded.
+ * - In V+, the default mode is MODE_DEFAULT. Unlike U-, this potentially affects *all* apps,
+ * not just the ones which have been explicitly marked as **guarded**.
+ *
+ * Regarding ECM "setting"s: a setting may be any abstract resource identified by a string. ECM
+ * may consider any particular setting **protected** or **not protected**. For now, the set of
+ * protected settings is hardcoded, but this may evolve in the future.
+ *
+ * TODO(b/320512579): These methods currently enforce UPDATE_APP_OPS_STATS,
+ * UPDATE_APP_OPS_STATS, and, for setter methods, MANAGE_APP_OPS_MODES. We should add
+ * RequiresPermission annotations, but we can't, because some of these permissions are hidden
+ * API. Either upgrade these to SystemApi or enforce a different permission, then add the
+ * appropriate RequiresPermission annotation.
+ */
+
+ /** A map of ECM states to their corresponding app op states */
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED,
+ EcmState.ECM_STATE_GUARDED, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED,
+ EcmState.ECM_STATE_IMPLICIT})
+ private @interface EcmState {
+ int ECM_STATE_NOT_GUARDED = AppOpsManager.MODE_ALLOWED;
+ int ECM_STATE_GUARDED = AppOpsManager.MODE_ERRORED;
+ int ECM_STATE_GUARDED_AND_ACKNOWLEDGED = AppOpsManager.MODE_IGNORED;
+ int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT;
+ }
+
+ private static final String LOG_TAG = EnhancedConfirmationManager.class.getSimpleName();
+
+ private static final ArraySet<String> PROTECTED_SETTINGS = new ArraySet<>();
+
+ static {
+ PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ // TODO(b/310654015): Add other explicitly protected settings
+ }
+
+ private final @NonNull Context mContext;
+ private final String mAttributionTag;
+ private final @NonNull UserHandle mUser;
+ private final AppOpsManager mAppOpsManager;
+ private final PackageManager mPackageManager;
+
+ /**
+ * @hide
+ */
+ public EnhancedConfirmationManager(@NonNull Context context) {
+ mContext = context;
+ mAttributionTag = context.getAttributionTag();
+ mUser = context.getUser();
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mPackageManager = context.getPackageManager();
+ }
+
+ /**
+ * Check whether a setting is restricted from an app.
+ *
+ * <p>This is {@code true} when the setting is a protected setting (i.e., a sensitive resource),
+ * and the app is restricted (i.e., considered dangerous), and the user has not yet cleared the
+ * app's restriction status (i.e., by clicking "Allow restricted settings" for this app).
+ *
+ * @param packageName package name of the application to check for
+ * @param settingIdentifier identifier of the resource to check to check for
+ * @return {@code true} if the setting is restricted from the app
+ * @throws NameNotFoundException if the provided package was not found
+ */
+ public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier)
+ throws NameNotFoundException {
+ return isSettingEcmProtected(settingIdentifier) && isPackageEcmGuarded(packageName);
+ }
+
+ /**
+ * Clear an app's restriction status (i.e., un-restrict it).
+ *
+ * <p>After this is called, the app will no longer be restricted from accessing any protected
+ * setting by ECM. This method should be called when the user clicks "Allow restricted settings"
+ * for the app.
+ *
+ * @param packageName package name of the application to remove protection from
+ * @throws NameNotFoundException if the provided package was not found
+ */
+ public void clearRestriction(@NonNull String packageName) throws NameNotFoundException {
+ if (!isClearRestrictionAllowed(packageName)) {
+ throw new IllegalStateException("Clear restriction attempted but not allowed");
+ }
+ setAppEcmState(packageName, EcmState.ECM_STATE_NOT_GUARDED);
+ }
+
+ /**
+ * Check whether the provided app is eligible to have its restriction status cleared (i.e., the
+ * app is restricted, and the "Restricted setting" dialog has been presented to the user).
+ *
+ * <p>The Settings UI should use method this to check whether to present the user with the
+ * "Allow restricted settings" button.
+ *
+ * @param packageName package name of the application to check for
+ * @return {@code true} if the settings UI should present the user with the ability to clear
+ * restrictions from the provided app
+ * @throws NameNotFoundException if the provided package was not found
+ */
+ public boolean isClearRestrictionAllowed(@NonNull String packageName)
+ throws NameNotFoundException {
+ int state = getAppEcmState(packageName);
+ return state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED;
+ }
+
+ /**
+ * Mark the app as eligible to have its restriction status cleared.
+ *
+ * <p>This should be called from the "Restricted setting" dialog (which {@link
+ * #getRestrictedSettingDialogIntent} directs to) upon being presented to the user.
+ *
+ * @param packageName package name of the application which should be considered acknowledged
+ * @throws NameNotFoundException if the provided package was not found
+ *
+ * @hide
+ */
+ public void setClearRestrictionAllowed(@NonNull String packageName)
+ throws NameNotFoundException {
+ if (isPackageEcmGuarded(packageName)) {
+ setAppEcmState(packageName, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED);
+ }
+ }
+
+ /**
+ * Gets an intent that will open the "Restricted setting" dialog for the specified app.
+ *
+ * <p>The "Restricted setting" dialog is a dialog that informs the user that the operation
+ * they've attempted to perform is restricted, and provides them with a link explaining how to
+ * proceed.
+ *
+ * @param packageName package name of the application to open the dialog for
+ * @throws NameNotFoundException if the provided package was not found
+ */
+ public @NonNull PendingIntent getRestrictedSettingDialogIntent(@NonNull String packageName)
+ throws NameNotFoundException {
+ Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(Intent.EXTRA_UID, getPackageUid(packageName));
+ return PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ private boolean isPackageEcmGuarded(@NonNull String packageName) throws NameNotFoundException {
+ // If this is a trusted installer or a pre-installed app, it is not ECM guarded
+ if (isAppTrustedInstaller(packageName) || isPackagePreinstalled(packageName)) {
+ return false;
+ }
+
+ // If the package already has an explicitly-set state, use that
+ @EcmState int ecmState = getAppEcmState(packageName);
+ if (ecmState == EcmState.ECM_STATE_GUARDED
+ || ecmState == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED) {
+ return true;
+ }
+ if (ecmState == EcmState.ECM_STATE_NOT_GUARDED) {
+ return false;
+ }
+
+ // Otherwise, lazily decide whether the app is considered guarded.
+ InstallSourceInfo installSource;
+ try {
+ installSource = mPackageManager.getInstallSourceInfo(packageName);
+ } catch (NameNotFoundException e) {
+ Log.w(LOG_TAG, "Package not found: " + packageName);
+ return false;
+ }
+
+ // These install sources are always considered dangerous.
+ // PackageInstallers that are trusted can use these as a signal that the
+ // packages they've installed aren't as trusted as themselves.
+ int packageSource = installSource.getPackageSource();
+ if (packageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+ || packageSource == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+ return true;
+ }
+
+ // ECM doesn't consider a transitive chain of trust for install sources.
+ // If this package hasn't been explicitly handled by this point
+ // then it is exempt from ECM if the immediate parent is a trusted installer
+ return installSource.getInstallingPackageName() != null && isAppTrustedInstaller(
+ installSource.getInstallingPackageName());
+ }
+
+ /**
+ * A "trusted installer" is any app with the INSTALL_PACKAGES permission.
+ */
+ private boolean isAppTrustedInstaller(@NonNull String packageName)
+ throws NameNotFoundException {
+ int uid = getPackageUid(packageName);
+ // TODO(b/310654834): Support allow-list for OEM installer exemptions
+ return mContext.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, 0, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean isPackagePreinstalled(@NonNull String packageName) {
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = mPackageManager.getApplicationInfoAsUser(packageName, 0, mUser);
+ } catch (NameNotFoundException e) {
+ Log.w(LOG_TAG, "Package not found: " + packageName);
+ return false;
+ }
+ return ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+ }
+
+ private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState)
+ throws NameNotFoundException {
+ mAppOpsManager.setMode(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ getPackageUid(packageName), packageName, ecmState);
+ }
+
+ private @EcmState int getAppEcmState(@NonNull String packageName) throws NameNotFoundException {
+ return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ getPackageUid(packageName), packageName, mAttributionTag, /* message */
+ null);
+ }
+
+ private boolean isSettingEcmProtected(@NonNull String settingIdentifier) {
+ if (PROTECTED_SETTINGS.contains(settingIdentifier)) {
+ return true;
+ }
+ // TODO(b/310654818): If this is a permission, coerce it into a PermissionGroup.
+ // TODO(b/310218979): Add role selections as protected settings
+ return false;
+ }
+
+ private int getPackageUid(String packageName) throws NameNotFoundException {
+ return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0, mUser).uid;
+ }
+}