Bug: 21589105 Rescope WRITE_SETTINGS permission (framework services perm check
changes)

AppOpsManager:
Changed the default operating mode for WRITE_SETTINGS to MODE_DEFAULT from
MODE_ALLOWED.

packages/SettingsProvider:
We no longer do static permission checks for WRITE_SETTINGS in early checks and
defer that to app op when MODE_DEFAULT is returned. For some operations,
checking against WRITE_SECURE_SETTINGS is sufficient.

ActivityManagerService & PowerManagerService:
Incorporated app op checks and handled the MODE_DEFAULT case.

provider/Settings:
Added helper function to do checks on whether app ops protected operations
can be performed by a caller. This includes checks for WRITE_SETTINGS and
SYSTEM_ALERT_WINDOW.
Also added a public API (with javadocs) for apps to query if they can modify
system settings.
Changed the javadocs description for ACTION_MANAGE_WRITE_SETTINGS and
ACTION_MANAGE_OVERLAY_PERMISSION.
Added public API (with javadocs) for apps to query whether they can draw overlays or not,
and also javadocs description on how to use that check.

Change-Id: I7b651fe8af836c2074defdbd6acfec3f32acdbe9
diff --git a/api/current.txt b/api/current.txt
index 7f788a5..72e0447 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26404,6 +26404,7 @@
 
   public final class Settings {
     ctor public Settings();
+    method public static boolean canDrawOverlays(android.content.Context);
     field public static final java.lang.String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
     field public static final java.lang.String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS";
     field public static final java.lang.String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS";
@@ -26623,6 +26624,7 @@
 
   public static final class Settings.System extends android.provider.Settings.NameValueTable {
     ctor public Settings.System();
+    method public static boolean canWrite(android.content.Context);
     method public static void getConfiguration(android.content.ContentResolver, android.content.res.Configuration);
     method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
     method public static float getFloat(android.content.ContentResolver, java.lang.String) throws android.provider.Settings.SettingNotFoundException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 4ef1719..65214b8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -28463,6 +28463,7 @@
 
   public final class Settings {
     ctor public Settings();
+    method public static boolean canDrawOverlays(android.content.Context);
     field public static final java.lang.String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
     field public static final java.lang.String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS";
     field public static final java.lang.String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS";
@@ -28683,6 +28684,7 @@
 
   public static final class Settings.System extends android.provider.Settings.NameValueTable {
     ctor public Settings.System();
+    method public static boolean canWrite(android.content.Context);
     method public static void getConfiguration(android.content.ContentResolver, android.content.res.Configuration);
     method public static float getFloat(android.content.ContentResolver, java.lang.String, float);
     method public static float getFloat(android.content.ContentResolver, java.lang.String) throws android.provider.Settings.SettingNotFoundException;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 976830fc..862d235 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -778,7 +778,7 @@
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,
-            AppOpsManager.MODE_ALLOWED,
+            AppOpsManager.MODE_DEFAULT, // OP_WRITE_SETTINGS
             AppOpsManager.MODE_DEFAULT, // OP_SYSTEM_ALERT_WINDOW
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 894d805..10470aa 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -20,6 +20,7 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.app.ActivityThread;
+import android.app.AppOpsManager;
 import android.app.Application;
 import android.app.SearchManager;
 import android.app.WallpaperManager;
@@ -41,6 +42,7 @@
 import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.DropBoxManager;
 import android.os.IBinder;
@@ -564,13 +566,14 @@
             "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
 
     /**
-     * Activity Action: Show settings to toggle permission to draw on top of
-     * other apps.
+     * Activity Action: Show screen for controlling which apps can draw on top of other apps.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
      * <p>
-     * Input: Nothing.
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
      * <p>
      * Output: Nothing.
      */
@@ -579,13 +582,15 @@
             "android.settings.action.MANAGE_OVERLAY_PERMISSION";
 
     /**
-     * Activity Action: Show settings to toggle apps' capablity to
-     * to read/write system settings.
+     * Activity Action: Show screen for controlling which apps are allowed to write/modify
+     * system settings.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
      * <p>
-     * Input: Nothing.
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
      * <p>
      * Output: Nothing.
      */
@@ -1386,6 +1391,23 @@
     }
 
     /**
+     * An app can use this method to check if it is currently allowed to draw on top of other
+     * apps. In order to be allowed to do so, an app must first declare the
+     * {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission in its manifest. If it
+     * is currently disallowed, it can prompt the user to grant it this capability through a
+     * management UI by sending an Intent with action
+     * {@link android.provider.Settings#ACTION_MANAGE_OVERLAY_PERMISSION}.
+     *
+     * @param context A context
+     * @return true if the calling app can draw on top of other apps, false otherwise.
+     */
+    public static boolean canDrawOverlays(Context context) {
+        int uid = Binder.getCallingUid();
+        return Settings.isCallingPackageAllowedToDrawOverlays(context, uid, Settings
+                .getPackageNameForUid(context, uid), false);
+    }
+
+    /**
      * System settings, containing miscellaneous system preferences.  This
      * table holds simple name/value pairs.  There are convenience
      * functions for accessing individual settings entries.
@@ -3658,6 +3680,23 @@
         @Deprecated
         public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS =
             Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS;
+
+        /**
+         * An app can use this method to check if it is currently allowed to write or modify system
+         * settings. In order to gain write access to the system settings, an app must declare the
+         * {@link android.Manifest.permission#WRITE_SETTINGS} permission in its manifest. If it is
+         * currently disallowed, it can prompt the user to grant it this capability through a
+         * management UI by sending an Intent with action
+         * {@link android.provider.Settings#ACTION_MANAGE_WRITE_SETTINGS}.
+         *
+         * @param context A context
+         * @return true if the calling app can write to system settings, false otherwise
+         */
+        public static boolean canWrite(Context context) {
+            int uid = Binder.getCallingUid();
+            return isCallingPackageAllowedToWriteSettings(context, uid, getPackageNameForUid(
+                    context, uid), false);
+        }
     }
 
     /**
@@ -8205,4 +8244,121 @@
     public static String getGTalkDeviceId(long androidId) {
         return "android-" + Long.toHexString(androidId);
     }
+
+    /**
+     * Performs a strict and comprehensive check of whether a calling package is allowed to
+     * write/modify system settings, as the condition differs for pre-M, M+, and
+     * privileged/preinstalled apps. If the provided uid does not match the
+     * callingPackage, a negative result will be returned.
+     * @hide
+     */
+    public static boolean isCallingPackageAllowedToWriteSettings(Context context, int uid,
+            String callingPackage, boolean throwException) {
+        return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
+                callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
+                android.Manifest.permission.WRITE_SETTINGS, false);
+    }
+
+    /**
+     * Performs a strict and comprehensive check of whether a calling package is allowed to
+     * write/modify system settings, as the condition differs for pre-M, M+, and
+     * privileged/preinstalled apps. If the provided uid does not match the
+     * callingPackage, a negative result will be returned.
+     *
+     * Note: if the check is successful, the operation of this app will be updated to the
+     * current time.
+     * @hide
+     */
+    public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid,
+            String callingPackage, boolean throwException) {
+        return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
+                callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
+                android.Manifest.permission.WRITE_SETTINGS, true);
+    }
+
+    /**
+     * Performs a strict and comprehensive check of whether a calling package is allowed to
+     * draw on top of other apps, as the conditions differs for pre-M, M+, and
+     * privileged/preinstalled apps. If the provided uid does not match the callingPackage,
+     * a negative result will be returned.
+     * @hide
+     */
+    public static boolean isCallingPackageAllowedToDrawOverlays(Context context, int uid,
+            String callingPackage, boolean throwException) {
+        return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
+                callingPackage, throwException, AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+                android.Manifest.permission.SYSTEM_ALERT_WINDOW, false);
+    }
+
+    /**
+     * Performs a strict and comprehensive check of whether a calling package is allowed to
+     * draw on top of other apps, as the conditions differs for pre-M, M+, and
+     * privileged/preinstalled apps. If the provided uid does not match the callingPackage,
+     * a negative result will be returned.
+     *
+     * Note: if the check is successful, the operation of this app will be updated to the
+     * current time.
+     * @hide
+     */
+    public static boolean checkAndNoteDrawOverlaysOperation(Context context, int uid, String
+            callingPackage, boolean throwException) {
+        return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
+                callingPackage, throwException, AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+                android.Manifest.permission.SYSTEM_ALERT_WINDOW, true);
+    }
+
+    /**
+     * Helper method to perform a general and comprehensive check of whether an operation that is
+     * protected by appops can be performed by a caller or not. e.g. OP_SYSTEM_ALERT_WINDOW and
+     * OP_WRITE_SETTINGS
+     * @hide
+     */
+    public static boolean isCallingPackageAllowedToPerformAppOpsProtectedOperation(Context context,
+            int uid, String callingPackage, boolean throwException, int appOpsOpCode, String
+            permissionName, boolean makeNote) {
+        if (callingPackage == null) {
+            return false;
+        }
+
+        AppOpsManager appOpsMgr = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        int mode = AppOpsManager.MODE_DEFAULT;
+        if (makeNote) {
+            mode = appOpsMgr.noteOpNoThrow(appOpsOpCode, uid, callingPackage);
+        } else {
+            mode = appOpsMgr.checkOpNoThrow(appOpsOpCode, uid, callingPackage);
+        }
+
+        switch (mode) {
+            case AppOpsManager.MODE_ALLOWED:
+                return true;
+            case AppOpsManager.MODE_DEFAULT:
+                // this is the default operating mode after an app's installation
+                if (!throwException) {
+                    return context.checkCallingOrSelfPermission(permissionName) ==
+                        PackageManager.PERMISSION_GRANTED;
+                }
+            default:
+                // this is for all other cases trickled down here...
+                if (!throwException) {
+                    return false;
+                }
+        }
+        throw new SecurityException(callingPackage + " was not granted "
+                + permissionName + " permission");
+    }
+
+    /**
+     * Retrieves a correponding package name for a given uid. It will query all
+     * packages that are associated with the given uid, but it will return only
+     * the zeroth result.
+     * Note: If package could not be found, a null is returned.
+     * @hide
+     */
+    public static String getPackageNameForUid(Context context, int uid) {
+        String[] packages = context.getPackageManager().getPackagesForUid(uid);
+        if (packages == null) {
+            return null;
+        }
+        return packages[0];
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 2a68252..3e9b122 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -643,11 +643,6 @@
         // Make sure the caller can change the settings - treated as secure.
         enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
-        // Verify whether this operation is allowed for the calling package.
-        if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
-            return false;
-        }
-
         // Resolve the userId on whose behalf the call is made.
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
 
@@ -773,11 +768,6 @@
         // Make sure the caller can change the settings.
         enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
-        // Verify whether this operation is allowed for the calling package.
-        if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
-            return false;
-        }
-
         // Resolve the userId on whose behalf the call is made.
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
 
@@ -904,14 +894,13 @@
 
     private boolean mutateSystemSetting(String name, String value, int runAsUserId,
             int operation) {
-        // Check for permissions first.
-        if (!hasPermissionsToMutateSystemSettings()) {
-            return false;
-        }
-
-        // Verify whether this operation is allowed for the calling package.
-        if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
-            return false;
+        if (!hasWriteSecureSettingsPermission()) {
+            // If the caller doesn't hold WRITE_SECURE_SETTINGS, we verify whether this
+            // operation is allowed for the calling package through appops.
+            if (!Settings.checkAndNoteWriteSettingsOperation(getContext(),
+                    Binder.getCallingUid(), getCallingPackage(), true)) {
+                return false;
+            }
         }
 
         // Enforce what the calling package can mutate the system settings.
@@ -956,25 +945,13 @@
         }
     }
 
-    private boolean hasPermissionsToMutateSystemSettings() {
+    private boolean hasWriteSecureSettingsPermission() {
         // Write secure settings is a more protected permission. If caller has it we are good.
         if (getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                 == PackageManager.PERMISSION_GRANTED) {
             return true;
         }
 
-        // The write settings permission gates mutation of system settings.
-        if (getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_SETTINGS)
-                == PackageManager.PERMISSION_GRANTED) {
-            return true;
-        }
-
-        // Excpet we let system apps change system settings without the permission.
-        PackageInfo packageInfo = getCallingPackageInfoOrThrow();
-        if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-            return true;
-        }
-
         return false;
     }
 
@@ -1102,15 +1079,6 @@
         }
     }
 
-    private boolean isAppOpWriteSettingsAllowedForCallingPackage() {
-        final int callingUid = Binder.getCallingUid();
-
-        mAppOpsManager.checkPackage(Binder.getCallingUid(), getCallingPackage());
-
-        return mAppOpsManager.noteOp(AppOpsManager.OP_WRITE_SETTINGS, callingUid,
-                getCallingPackage()) == AppOpsManager.MODE_ALLOWED;
-    }
-
     private void enforceWritePermission(String permission) {
         if (getContext().checkCallingOrSelfPermission(permission)
                 != PackageManager.PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 89e500e..783dea5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17201,8 +17201,7 @@
     public void updatePersistentConfiguration(Configuration values) {
         enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                 "updateConfiguration()");
-        enforceCallingPermission(android.Manifest.permission.WRITE_SETTINGS,
-                "updateConfiguration()");
+        enforceWriteSettingsPermission("updateConfiguration()");
         if (values == null) {
             throw new NullPointerException("Configuration must not be null");
         }
@@ -17214,6 +17213,25 @@
         }
     }
 
+    private void enforceWriteSettingsPermission(String func) {
+        int uid = Binder.getCallingUid();
+        if (uid == Process.ROOT_UID) {
+            return;
+        }
+
+        if (Settings.checkAndNoteWriteSettingsOperation(mContext, uid,
+                Settings.getPackageNameForUid(mContext, uid), false)) {
+            return;
+        }
+
+        String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + uid
+                + " requires " + android.Manifest.permission.WRITE_SETTINGS;
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
+    }
+
     public void updateConfiguration(Configuration values) {
         enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                 "updateConfiguration()");
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 88476ce..b920f97 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -30,6 +30,7 @@
 import com.android.server.Watchdog;
 
 import android.Manifest;
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -3319,8 +3320,14 @@
          */
         @Override // Binder call
         public void setStayOnSetting(int val) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.WRITE_SETTINGS, null);
+            int uid = Binder.getCallingUid();
+            // if uid is of root's, we permit this operation straight away
+            if (uid != Process.ROOT_UID) {
+                if (!Settings.checkAndNoteWriteSettingsOperation(mContext, uid,
+                        Settings.getPackageNameForUid(mContext, uid), true)) {
+                    return;
+                }
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {