diff options
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | api/system-current.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/AppOpsManager.java | 2 | ||||
-rw-r--r-- | core/java/android/provider/Settings.java | 168 | ||||
-rw-r--r-- | packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java | 48 | ||||
-rw-r--r-- | services/core/java/com/android/server/am/ActivityManagerService.java | 22 | ||||
-rw-r--r-- | services/core/java/com/android/server/power/PowerManagerService.java | 11 |
7 files changed, 204 insertions, 51 deletions
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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 976830f..862d235 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -778,7 +778,7 @@ public class AppOpsManager { 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; 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.ConnectivityManager; 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 @@ public final class Settings { "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 @@ public final class Settings { "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 @@ public final class Settings { } /** + * 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 @@ public final class Settings { @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 final class Settings { 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { } } - 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 @@ public class SettingsProvider extends ContentProvider { } } - 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 final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative } } + 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.lights.LightsManager; 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 @@ public final class PowerManagerService extends SystemService */ @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 { |