diff options
| author | 2020-03-10 09:49:22 -0700 | |
|---|---|---|
| committer | 2020-03-10 10:06:02 -0700 | |
| commit | ad787aa1285acfbf4a8435b7057d83dcaa36c563 (patch) | |
| tree | 11d5dec4666c03ca048b71c588e41d2863ce3ea3 | |
| parent | 55bd691d2b180300b61562622646ce75493eba3a (diff) | |
Allow to exempt apps from restrictions to RECORD_AUDIO
- Extend the concept if "isPrivilidged" to a generic "RestrictionBypass"
class so that we can add more variants of bypassing.
- Add a new way of bypassing by using a new permission.
- Always except resolvable UIDs (root, shell, media_uid, audioserver,
cameraserver, system server) from restrictions.
Fixes: 141210120
Test: atest -m CtsAppOpsTestCases FrameworksMockingServicesTests:AppOpsServiceTest FrameworksMockingServicesTests:AppOpsUpgradeTest
Change-Id: I3ca555e9370aa0003400429ee7ab12e95c62a042
| -rwxr-xr-x | api/system-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/app/AppOpsManager.java | 233 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 10 | ||||
| -rw-r--r-- | core/res/res/values/strings.xml | 8 | ||||
| -rw-r--r-- | services/core/java/com/android/server/appop/AppOpsService.java | 223 |
5 files changed, 235 insertions, 240 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 25f16d3c99fc..9510a3f4c537 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -79,6 +79,7 @@ package android { field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED"; + field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS"; field public static final String FORCE_BACK = "android.permission.FORCE_BACK"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index e2ecf85b7af8..4bc1fa0c2690 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1988,108 +1988,108 @@ public class AppOpsManager { }; /** - * This specifies whether each option should allow the system - * (and system ui) to bypass the user restriction when active. - */ - private static boolean[] sOpAllowSystemRestrictionBypass = new boolean[] { - true, //COARSE_LOCATION - true, //FINE_LOCATION - false, //GPS - false, //VIBRATE - false, //READ_CONTACTS - false, //WRITE_CONTACTS - false, //READ_CALL_LOG - false, //WRITE_CALL_LOG - false, //READ_CALENDAR - false, //WRITE_CALENDAR - true, //WIFI_SCAN - false, //POST_NOTIFICATION - false, //NEIGHBORING_CELLS - false, //CALL_PHONE - false, //READ_SMS - false, //WRITE_SMS - false, //RECEIVE_SMS - false, //RECEIVE_EMERGECY_SMS - false, //RECEIVE_MMS - false, //RECEIVE_WAP_PUSH - false, //SEND_SMS - false, //READ_ICC_SMS - false, //WRITE_ICC_SMS - false, //WRITE_SETTINGS - true, //SYSTEM_ALERT_WINDOW - false, //ACCESS_NOTIFICATIONS - false, //CAMERA - false, //RECORD_AUDIO - false, //PLAY_AUDIO - false, //READ_CLIPBOARD - false, //WRITE_CLIPBOARD - false, //TAKE_MEDIA_BUTTONS - false, //TAKE_AUDIO_FOCUS - false, //AUDIO_MASTER_VOLUME - false, //AUDIO_VOICE_VOLUME - false, //AUDIO_RING_VOLUME - false, //AUDIO_MEDIA_VOLUME - false, //AUDIO_ALARM_VOLUME - false, //AUDIO_NOTIFICATION_VOLUME - false, //AUDIO_BLUETOOTH_VOLUME - false, //WAKE_LOCK - false, //MONITOR_LOCATION - false, //MONITOR_HIGH_POWER_LOCATION - false, //GET_USAGE_STATS - false, //MUTE_MICROPHONE - true, //TOAST_WINDOW - false, //PROJECT_MEDIA - false, //ACTIVATE_VPN - false, //WALLPAPER - false, //ASSIST_STRUCTURE - false, //ASSIST_SCREENSHOT - false, //READ_PHONE_STATE - false, //ADD_VOICEMAIL - false, // USE_SIP - false, // PROCESS_OUTGOING_CALLS - false, // USE_FINGERPRINT - false, // BODY_SENSORS - false, // READ_CELL_BROADCASTS - false, // MOCK_LOCATION - false, // READ_EXTERNAL_STORAGE - false, // WRITE_EXTERNAL_STORAGE - false, // TURN_ON_SCREEN - false, // GET_ACCOUNTS - false, // RUN_IN_BACKGROUND - false, // AUDIO_ACCESSIBILITY_VOLUME - false, // READ_PHONE_NUMBERS - false, // REQUEST_INSTALL_PACKAGES - false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE - false, // INSTANT_APP_START_FOREGROUND - false, // ANSWER_PHONE_CALLS - false, // OP_RUN_ANY_IN_BACKGROUND - false, // OP_CHANGE_WIFI_STATE - false, // OP_REQUEST_DELETE_PACKAGES - false, // OP_BIND_ACCESSIBILITY_SERVICE - false, // ACCEPT_HANDOVER - false, // MANAGE_IPSEC_HANDOVERS - false, // START_FOREGROUND - true, // BLUETOOTH_SCAN - false, // USE_BIOMETRIC - false, // ACTIVITY_RECOGNITION - false, // SMS_FINANCIAL_TRANSACTIONS - false, // READ_MEDIA_AUDIO - false, // WRITE_MEDIA_AUDIO - false, // READ_MEDIA_VIDEO - false, // WRITE_MEDIA_VIDEO - false, // READ_MEDIA_IMAGES - false, // WRITE_MEDIA_IMAGES - false, // LEGACY_STORAGE - false, // ACCESS_ACCESSIBILITY - false, // READ_DEVICE_IDENTIFIERS - false, // ACCESS_MEDIA_LOCATION - false, // QUERY_ALL_PACKAGES - false, // MANAGE_EXTERNAL_STORAGE - false, // INTERACT_ACROSS_PROFILES - false, // ACTIVATE_PLATFORM_VPN - false, // LOADER_USAGE_STATS - false, // ACCESS_CALL_AUDIO - false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED + * In which cases should an app be allowed to bypass the {@link #setUserRestriction user + * restriction} for a certain app-op. + */ + private static RestrictionBypass[] sOpAllowSystemRestrictionBypass = new RestrictionBypass[] { + new RestrictionBypass(true, false), //COARSE_LOCATION + new RestrictionBypass(true, false), //FINE_LOCATION + null, //GPS + null, //VIBRATE + null, //READ_CONTACTS + null, //WRITE_CONTACTS + null, //READ_CALL_LOG + null, //WRITE_CALL_LOG + null, //READ_CALENDAR + null, //WRITE_CALENDAR + new RestrictionBypass(true, false), //WIFI_SCAN + null, //POST_NOTIFICATION + null, //NEIGHBORING_CELLS + null, //CALL_PHONE + null, //READ_SMS + null, //WRITE_SMS + null, //RECEIVE_SMS + null, //RECEIVE_EMERGECY_SMS + null, //RECEIVE_MMS + null, //RECEIVE_WAP_PUSH + null, //SEND_SMS + null, //READ_ICC_SMS + null, //WRITE_ICC_SMS + null, //WRITE_SETTINGS + new RestrictionBypass(true, false), //SYSTEM_ALERT_WINDOW + null, //ACCESS_NOTIFICATIONS + null, //CAMERA + new RestrictionBypass(false, true), //RECORD_AUDIO + null, //PLAY_AUDIO + null, //READ_CLIPBOARD + null, //WRITE_CLIPBOARD + null, //TAKE_MEDIA_BUTTONS + null, //TAKE_AUDIO_FOCUS + null, //AUDIO_MASTER_VOLUME + null, //AUDIO_VOICE_VOLUME + null, //AUDIO_RING_VOLUME + null, //AUDIO_MEDIA_VOLUME + null, //AUDIO_ALARM_VOLUME + null, //AUDIO_NOTIFICATION_VOLUME + null, //AUDIO_BLUETOOTH_VOLUME + null, //WAKE_LOCK + null, //MONITOR_LOCATION + null, //MONITOR_HIGH_POWER_LOCATION + null, //GET_USAGE_STATS + null, //MUTE_MICROPHONE + new RestrictionBypass(true, false), //TOAST_WINDOW + null, //PROJECT_MEDIA + null, //ACTIVATE_VPN + null, //WALLPAPER + null, //ASSIST_STRUCTURE + null, //ASSIST_SCREENSHOT + null, //READ_PHONE_STATE + null, //ADD_VOICEMAIL + null, // USE_SIP + null, // PROCESS_OUTGOING_CALLS + null, // USE_FINGERPRINT + null, // BODY_SENSORS + null, // READ_CELL_BROADCASTS + null, // MOCK_LOCATION + null, // READ_EXTERNAL_STORAGE + null, // WRITE_EXTERNAL_STORAGE + null, // TURN_ON_SCREEN + null, // GET_ACCOUNTS + null, // RUN_IN_BACKGROUND + null, // AUDIO_ACCESSIBILITY_VOLUME + null, // READ_PHONE_NUMBERS + null, // REQUEST_INSTALL_PACKAGES + null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE + null, // INSTANT_APP_START_FOREGROUND + null, // ANSWER_PHONE_CALLS + null, // OP_RUN_ANY_IN_BACKGROUND + null, // OP_CHANGE_WIFI_STATE + null, // OP_REQUEST_DELETE_PACKAGES + null, // OP_BIND_ACCESSIBILITY_SERVICE + null, // ACCEPT_HANDOVER + null, // MANAGE_IPSEC_HANDOVERS + null, // START_FOREGROUND + new RestrictionBypass(true, false), // BLUETOOTH_SCAN + null, // USE_BIOMETRIC + null, // ACTIVITY_RECOGNITION + null, // SMS_FINANCIAL_TRANSACTIONS + null, // READ_MEDIA_AUDIO + null, // WRITE_MEDIA_AUDIO + null, // READ_MEDIA_VIDEO + null, // WRITE_MEDIA_VIDEO + null, // READ_MEDIA_IMAGES + null, // WRITE_MEDIA_IMAGES + null, // LEGACY_STORAGE + null, // ACCESS_ACCESSIBILITY + null, // READ_DEVICE_IDENTIFIERS + null, // ACCESS_MEDIA_LOCATION + null, // QUERY_ALL_PACKAGES + null, // MANAGE_EXTERNAL_STORAGE + null, // INTERACT_ACROSS_PROFILES + null, // ACTIVATE_PLATFORM_VPN + null, // LOADER_USAGE_STATS + null, // ACCESS_CALL_AUDIO + null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED }; /** @@ -2485,11 +2485,11 @@ public class AppOpsManager { } /** - * Retrieve whether the op allows the system (and system ui) to - * bypass the user restriction. + * Retrieve whether the op allows to bypass the user restriction. + * * @hide */ - public static boolean opAllowSystemBypassRestriction(int op) { + public static RestrictionBypass opAllowSystemBypassRestriction(int op) { return sOpAllowSystemRestrictionBypass[op]; } @@ -2536,6 +2536,29 @@ public class AppOpsManager { } /** + * When to not enforce {@link #setUserRestriction restrictions}. + * + * @hide + */ + public static class RestrictionBypass { + /** Does the app need to be privileged to bypass the restriction */ + public boolean isPrivileged; + + /** + * Does the app need to have the EXEMPT_FROM_AUDIO_RESTRICTIONS permission to bypass the + * restriction + */ + public boolean isRecordAudioRestrictionExcept; + + public RestrictionBypass(boolean isPrivileged, boolean isRecordAudioRestrictionExcept) { + this.isPrivileged = isPrivileged; + this.isRecordAudioRestrictionExcept = isRecordAudioRestrictionExcept; + } + + public static RestrictionBypass UNRESTRICTED = new RestrictionBypass(true, true); + } + + /** * Class holding all of the operation information associated with an app. * @hide */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 885117018c50..ed3fb07a3e6d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1187,6 +1187,16 @@ android:description="@string/permdesc_callCompanionApp" android:protectionLevel="normal" /> + <!-- Exempt this uid from restrictions to background audio recoding + <p>Protection level: signature|privileged + @hide + @SystemApi + --> + <permission android:name="android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS" + android:label="@string/permlab_exemptFromAudioRecordRestrictions" + android:description="@string/permdesc_exemptFromAudioRecordRestrictions" + android:protectionLevel="signature|privileged" /> + <!-- Allows a calling app to continue a call which was started in another app. An example is a video calling app that wants to continue a voice call on the user's mobile network.<p> When the handover of a call from one app to another takes place, there are two devices diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d30fdced3e38..f7206b933889 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1212,6 +1212,14 @@ device. This includes information such as call numbers for calls and the state of the calls.</string> + <!-- Title of an application permission. When granted the app is exempt from audio record + restrictions. + [CHAR LIMIT=NONE]--> + <string name="permlab_exemptFromAudioRecordRestrictions">exempt from audio record restrictions</string> + <!-- Description of an application permission. When granted the app is exempt from audio record + restrictions. [CHAR LIMIT=NONE]--> + <string name="permdesc_exemptFromAudioRecordRestrictions">Exempt the app from restrictions to record audio.</string> + <!-- Title of an application permission. When granted the user is giving access to a third party app to continue a call which originated in another app. For example, the user could be in a voice call over their carrier's mobile network, and a third party video diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7774633fa1be..471c97b0e7c6 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -40,6 +40,7 @@ import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OpEventProxyInfo; +import static android.app.AppOpsManager.RestrictionBypass; import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.UID_STATE_BACKGROUND; @@ -55,6 +56,7 @@ import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; import static android.app.AppOpsManager.makeKey; import static android.app.AppOpsManager.modeToName; +import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState; @@ -73,7 +75,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; -import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManager.HistoricalOps; @@ -92,8 +93,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -666,15 +665,19 @@ public class AppOpsService extends IAppOpsService.Stub { final static class Ops extends SparseArray<Op> { final String packageName; final UidState uidState; - final boolean isPrivileged; + + /** + * The restriction properties of the package. If {@code null} it could not have been read + * yet and has to be refreshed. + */ + @Nullable RestrictionBypass bypass; /** Lazily populated cache of featureIds of this package */ final @NonNull ArraySet<String> knownFeatureIds = new ArraySet<>(); - Ops(String _packageName, UidState _uidState, boolean _isPrivileged) { + Ops(String _packageName, UidState _uidState) { packageName = _packageName; uidState = _uidState; - isPrivileged = _isPrivileged; } } @@ -1519,7 +1522,11 @@ public class AppOpsService extends IAppOpsService.Stub { return; } + // Reset cached package properties to re-initialize when needed + ops.bypass = null; ops.knownFeatureIds.clear(); + + // Merge data collected for removed features into their successor features int numOps = ops.size(); for (int opNum = 0; opNum < numOps; opNum++) { Op op = ops.valueAt(opNum); @@ -1953,8 +1960,7 @@ public class AppOpsService extends IAppOpsService.Stub { return Collections.emptyList(); } synchronized (this) { - Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, null, false /* isPrivileged */, - false /* edit */); + Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, null, false /* edit */); if (pkgOps == null) { return null; } @@ -2087,8 +2093,7 @@ public class AppOpsService extends IAppOpsService.Stub { op.removeFeaturesWithNoTime(); if (op.mFeatures.size() == 0) { - Ops ops = getOpsRawLocked(uid, packageName, null, false /* isPrivileged */, - false /* edit */); + Ops ops = getOpsLocked(uid, packageName, null, null, false /* edit */); if (ops != null) { ops.remove(op.op); if (ops.size() <= 0) { @@ -2390,9 +2395,9 @@ public class AppOpsService extends IAppOpsService.Stub { ArraySet<ModeCallback> repCbs = null; code = AppOpsManager.opToSwitch(code); - boolean isPrivileged; + RestrictionBypass bypass; try { - isPrivileged = verifyAndGetIsPrivileged(uid, packageName, null); + bypass = verifyAndGetBypass(uid, packageName, null); } catch (SecurityException e) { Slog.e(TAG, "Cannot setMode", e); return; @@ -2400,7 +2405,7 @@ public class AppOpsService extends IAppOpsService.Stub { synchronized (this) { UidState uidState = getUidStateLocked(uid, false); - Op op = getOpLocked(code, uid, packageName, null, isPrivileged, true); + Op op = getOpLocked(code, uid, packageName, null, bypass, true); if (op != null) { if (op.mode != mode) { op.mode = mode; @@ -2798,10 +2803,9 @@ public class AppOpsService extends IAppOpsService.Stub { */ private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName, boolean raw) { - boolean isPrivileged; - + RestrictionBypass bypass; try { - isPrivileged = verifyAndGetIsPrivileged(uid, packageName, null); + bypass = verifyAndGetBypass(uid, packageName, null); } catch (SecurityException e) { Slog.e(TAG, "checkOperation", e); return AppOpsManager.opToDefaultMode(code); @@ -2811,7 +2815,7 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } synchronized (this) { - if (isOpRestrictedLocked(uid, code, packageName, null, isPrivileged)) { + if (isOpRestrictedLocked(uid, code, packageName, bypass)) { return AppOpsManager.MODE_IGNORED; } code = AppOpsManager.opToSwitch(code); @@ -2821,7 +2825,7 @@ public class AppOpsService extends IAppOpsService.Stub { final int rawMode = uidState.opModes.get(code); return raw ? rawMode : uidState.evalMode(code, rawMode); } - Op op = getOpLocked(code, uid, packageName, null, false, false); + Op op = getOpLocked(code, uid, packageName, null, bypass, false); if (op == null) { return AppOpsManager.opToDefaultMode(code); } @@ -2884,7 +2888,7 @@ public class AppOpsService extends IAppOpsService.Stub { public int checkPackage(int uid, String packageName) { Objects.requireNonNull(packageName); try { - verifyAndGetIsPrivileged(uid, packageName, null); + verifyAndGetBypass(uid, packageName, null); return AppOpsManager.MODE_ALLOWED; } catch (SecurityException ignored) { @@ -2961,17 +2965,16 @@ public class AppOpsService extends IAppOpsService.Stub { @Nullable String featureId, int proxyUid, String proxyPackageName, @Nullable String proxyFeatureId, @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message) { - boolean isPrivileged; + RestrictionBypass bypass; try { - isPrivileged = verifyAndGetIsPrivileged(uid, packageName, featureId); + bypass = verifyAndGetBypass(uid, packageName, featureId); } catch (SecurityException e) { Slog.e(TAG, "noteOperation", e); return AppOpsManager.MODE_ERRORED; } synchronized (this) { - final Ops ops = getOpsRawLocked(uid, packageName, featureId, isPrivileged, - true /* edit */); + final Ops ops = getOpsLocked(uid, packageName, featureId, bypass, true /* edit */); if (ops == null) { scheduleOpNotedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_IGNORED); @@ -2981,7 +2984,7 @@ public class AppOpsService extends IAppOpsService.Stub { } final Op op = getOpLocked(ops, code, uid, true); final FeatureOp featureOp = op.getOrCreateFeature(op, featureId); - if (isOpRestrictedLocked(uid, code, packageName, featureId, isPrivileged)) { + if (isOpRestrictedLocked(uid, code, packageName, bypass)) { scheduleOpNotedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_IGNORED); return AppOpsManager.MODE_IGNORED; @@ -3205,7 +3208,7 @@ public class AppOpsService extends IAppOpsService.Stub { int uid = Binder.getCallingUid(); Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid); - verifyAndGetIsPrivileged(uid, packageName, null); + verifyAndGetBypass(uid, packageName, null); synchronized (this) { RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key); @@ -3235,7 +3238,7 @@ public class AppOpsService extends IAppOpsService.Stub { int uid = Binder.getCallingUid(); Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid); - verifyAndGetIsPrivileged(uid, packageName, null); + verifyAndGetBypass(uid, packageName, null); synchronized (this) { RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key); @@ -3254,7 +3257,7 @@ public class AppOpsService extends IAppOpsService.Stub { int uid = Binder.getCallingUid(); - verifyAndGetIsPrivileged(uid, packageName, null); + verifyAndGetBypass(uid, packageName, null); synchronized (this) { return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid)); @@ -3272,16 +3275,16 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } - boolean isPrivileged; + RestrictionBypass bypass; try { - isPrivileged = verifyAndGetIsPrivileged(uid, packageName, featureId); + bypass = verifyAndGetBypass(uid, packageName, featureId); } catch (SecurityException e) { Slog.e(TAG, "startOperation", e); return AppOpsManager.MODE_ERRORED; } synchronized (this) { - final Ops ops = getOpsRawLocked(uid, resolvedPackageName, featureId, isPrivileged, + final Ops ops = getOpsLocked(uid, resolvedPackageName, featureId, bypass, true /* edit */); if (ops == null) { if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid @@ -3289,7 +3292,7 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_ERRORED; } final Op op = getOpLocked(ops, code, uid, true); - if (isOpRestrictedLocked(uid, code, resolvedPackageName, featureId, isPrivileged)) { + if (isOpRestrictedLocked(uid, code, resolvedPackageName, bypass)) { return AppOpsManager.MODE_IGNORED; } final FeatureOp featureOp = op.getOrCreateFeature(op, featureId); @@ -3347,16 +3350,16 @@ public class AppOpsService extends IAppOpsService.Stub { return; } - boolean isPrivileged; + RestrictionBypass bypass; try { - isPrivileged = verifyAndGetIsPrivileged(uid, packageName, featureId); + bypass = verifyAndGetBypass(uid, packageName, featureId); } catch (SecurityException e) { Slog.e(TAG, "Cannot finishOperation", e); return; } synchronized (this) { - Op op = getOpLocked(code, uid, resolvedPackageName, featureId, isPrivileged, true); + Op op = getOpLocked(code, uid, resolvedPackageName, featureId, bypass, true); if (op == null) { return; } @@ -3617,7 +3620,22 @@ public class AppOpsService extends IAppOpsService.Stub { } /** - * Verify that package belongs to uid and return whether the package is privileged. + * Create a restriction description matching the properties of the package. + * + * @param context A context to use + * @param pkg The package to create the restriction description for + * + * @return The restriction matching the package + */ + private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) { + return new RestrictionBypass(pkg.isPrivileged(), mContext.checkPermission( + android.Manifest.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid()) + == PackageManager.PERMISSION_GRANTED); + } + + /** + * Verify that package belongs to uid and return the {@link RestrictionBypass bypass + * description} for the package. * * @param uid The uid the package belongs to * @param packageName The package the might belong to the uid @@ -3625,11 +3643,11 @@ public class AppOpsService extends IAppOpsService.Stub { * * @return {@code true} iff the package is privileged */ - private boolean verifyAndGetIsPrivileged(int uid, String packageName, + private @Nullable RestrictionBypass verifyAndGetBypass(int uid, String packageName, @Nullable String featureId) { if (uid == Process.ROOT_UID) { // For backwards compatibility, don't check package name for root UID. - return false; + return null; } // Do not check if uid/packageName/featureId is already known @@ -3638,13 +3656,14 @@ public class AppOpsService extends IAppOpsService.Stub { if (uidState != null && uidState.pkgOps != null) { Ops ops = uidState.pkgOps.get(packageName); - if (ops != null && (featureId == null || ops.knownFeatureIds.contains(featureId))) { - return ops.isPrivileged; + if (ops != null && (featureId == null || ops.knownFeatureIds.contains(featureId)) + && ops.bypass != null) { + return ops.bypass; } } } - boolean isPrivileged = false; + RestrictionBypass bypass = null; final long ident = Binder.clearCallingIdentity(); try { int pkgUid; @@ -3668,14 +3687,14 @@ public class AppOpsService extends IAppOpsService.Stub { pkgUid = UserHandle.getUid( UserHandle.getUserId(uid), UserHandle.getAppId(pkg.getUid())); - isPrivileged = pkg.isPrivileged(); + bypass = getBypassforPackage(pkg); } else { // Allow any feature id for resolvable uids isFeatureIdValid = true; pkgUid = resolveUid(packageName); if (pkgUid >= 0) { - isPrivileged = false; + bypass = RestrictionBypass.UNRESTRICTED; } } if (pkgUid != uid) { @@ -3692,7 +3711,7 @@ public class AppOpsService extends IAppOpsService.Stub { Binder.restoreCallingIdentity(ident); } - return isPrivileged; + return bypass; } /** @@ -3701,13 +3720,13 @@ public class AppOpsService extends IAppOpsService.Stub { * @param uid The uid the package belongs to * @param packageName The name of the package * @param featureId The feature in the package - * @param isPrivileged If the package is privilidged (ignored if {@code edit} is false) + * @param bypass When to bypass certain op restrictions (can be null if edit == false) * @param edit If an ops does not exist, create the ops? - * @return + * @return The ops */ - private Ops getOpsRawLocked(int uid, String packageName, @Nullable String featureId, - boolean isPrivileged, boolean edit) { + private Ops getOpsLocked(int uid, String packageName, @Nullable String featureId, + @Nullable RestrictionBypass bypass, boolean edit) { UidState uidState = getUidStateLocked(uid, edit); if (uidState == null) { return null; @@ -3725,53 +3744,18 @@ public class AppOpsService extends IAppOpsService.Stub { if (!edit) { return null; } - ops = new Ops(packageName, uidState, isPrivileged); + ops = new Ops(packageName, uidState); uidState.pkgOps.put(packageName, ops); } - if (edit && featureId != null) { - ops.knownFeatureIds.add(featureId); - } - return ops; - } - - /** - * Get the state of all ops for a package. - * - * <p>Usually callers should use {@link #getOpLocked} and not call this directly. - * - * @param uid The uid the of the package - * @param packageName The package name for which to get the state for - * @param featureId The feature in the package - * @param edit Iff {@code true} create the {@link Ops} object if not yet created - * @param isPrivileged Whether the package is privileged or not - * - * @return The {@link Ops state} of all ops for the package - */ - private @Nullable Ops getOpsRawNoVerifyLocked(int uid, @NonNull String packageName, - @Nullable String featureId, boolean edit, boolean isPrivileged) { - UidState uidState = getUidStateLocked(uid, edit); - if (uidState == null) { - return null; - } - if (uidState.pkgOps == null) { - if (!edit) { - return null; + if (edit) { + if (bypass != null) { + ops.bypass = bypass; } - uidState.pkgOps = new ArrayMap<>(); - } - Ops ops = uidState.pkgOps.get(packageName); - if (ops == null) { - if (!edit) { - return null; + if (featureId != null) { + ops.knownFeatureIds.add(featureId); } - ops = new Ops(packageName, uidState, isPrivileged); - uidState.pkgOps.put(packageName, ops); - } - - if (edit && featureId != null) { - ops.knownFeatureIds.add(featureId); } return ops; @@ -3800,15 +3784,14 @@ public class AppOpsService extends IAppOpsService.Stub { * @param uid The uid the of the package * @param packageName The package name for which to get the state for * @param featureId The feature in the package - * @param isPrivileged Whether the package is privileged or not (only used if {@code edit - * == true}) + * @param bypass When to bypass certain op restrictions (can be null if edit == false) * @param edit Iff {@code true} create the {@link Op} object if not yet created * * @return The {@link Op state} of the op */ private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName, - @Nullable String featureId, boolean isPrivileged, boolean edit) { - Ops ops = getOpsRawNoVerifyLocked(uid, packageName, featureId, edit, isPrivileged); + @Nullable String featureId, @Nullable RestrictionBypass bypass, boolean edit) { + Ops ops = getOpsLocked(uid, packageName, featureId, bypass, edit); if (ops == null) { return null; } @@ -3839,7 +3822,7 @@ public class AppOpsService extends IAppOpsService.Stub { } private boolean isOpRestrictedLocked(int uid, int code, String packageName, - @Nullable String featureId, boolean isPrivileged) { + @Nullable RestrictionBypass appBypass) { int userHandle = UserHandle.getUserId(uid); final int restrictionSetCount = mOpUserRestrictions.size(); @@ -3848,12 +3831,15 @@ public class AppOpsService extends IAppOpsService.Stub { // package is exempt from the restriction. ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i); if (restrictionState.hasRestriction(code, packageName, userHandle)) { - if (AppOpsManager.opAllowSystemBypassRestriction(code)) { + RestrictionBypass opBypass = opAllowSystemBypassRestriction(code); + if (opBypass != null) { // If we are the system, bypass user restrictions for certain codes synchronized (this) { - Ops ops = getOpsRawLocked(uid, packageName, featureId, isPrivileged, - true /* edit */); - if ((ops != null) && ops.isPrivileged) { + if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) { + return false; + } + if (opBypass.isRecordAudioRestrictionExcept && appBypass != null + && appBypass.isRecordAudioRestrictionExcept) { return false; } } @@ -4043,28 +4029,6 @@ public class AppOpsService extends IAppOpsService.Stub { throws NumberFormatException, XmlPullParserException, IOException { int uid = Integer.parseInt(parser.getAttributeValue(null, "n")); final UidState uidState = getUidStateLocked(uid, true); - String isPrivilegedString = parser.getAttributeValue(null, "p"); - boolean isPrivileged = false; - if (isPrivilegedString == null) { - try { - IPackageManager packageManager = ActivityThread.getPackageManager(); - if (packageManager != null) { - ApplicationInfo appInfo = ActivityThread.getPackageManager() - .getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid)); - if (appInfo != null) { - isPrivileged = (appInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; - } - } else { - // Could not load data, don't add to cache so it will be loaded later. - return; - } - } catch (RemoteException e) { - Slog.w(TAG, "Could not contact PackageManager", e); - } - } else { - isPrivileged = Boolean.parseBoolean(isPrivilegedString); - } int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -4074,7 +4038,7 @@ public class AppOpsService extends IAppOpsService.Stub { } String tagName = parser.getName(); if (tagName.equals("op")) { - readOp(parser, uidState, pkgName, isPrivileged); + readOp(parser, uidState, pkgName); } else { Slog.w(TAG, "Unknown element under <pkg>: " + parser.getName()); @@ -4108,8 +4072,8 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private void readOp(XmlPullParser parser, @NonNull UidState uidState, - @NonNull String pkgName, boolean isPrivileged) throws NumberFormatException, + private void readOp(XmlPullParser parser, @NonNull UidState uidState, @NonNull String pkgName) + throws NumberFormatException, XmlPullParserException, IOException { int opCode = Integer.parseInt(parser.getAttributeValue(null, "n")); if (isIgnoredAppOp(opCode)) { @@ -4143,7 +4107,7 @@ public class AppOpsService extends IAppOpsService.Stub { } Ops ops = uidState.pkgOps.get(pkgName); if (ops == null) { - ops = new Ops(pkgName, uidState, isPrivileged); + ops = new Ops(pkgName, uidState); uidState.pkgOps.put(pkgName, ops); } ops.put(op.op, op); @@ -4235,17 +4199,6 @@ public class AppOpsService extends IAppOpsService.Stub { } out.startTag(null, "uid"); out.attribute(null, "n", Integer.toString(pkg.getUid())); - synchronized (this) { - Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), null, - false /* isPrivileged */, false /* edit */); - // Should always be present as the list of PackageOps is generated - // from Ops. - if (ops != null) { - out.attribute(null, "p", Boolean.toString(ops.isPrivileged)); - } else { - out.attribute(null, "p", Boolean.toString(false)); - } - } List<AppOpsManager.OpEntry> ops = pkg.getOps(); for (int j=0; j<ops.size(); j++) { AppOpsManager.OpEntry op = ops.get(j); @@ -5574,7 +5527,7 @@ public class AppOpsService extends IAppOpsService.Stub { } // TODO moltmann: Allow to check for feature op activeness synchronized (AppOpsService.this) { - Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, null, false, false); + Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, null, false); if (pkgOps == null) { return false; } |