Merge "Allow per-package exemptions for restricted AppOps" into nyc-dev
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 82c4c51..64586a6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1267,8 +1267,15 @@
/** @hide */
public void setUserRestriction(int code, boolean restricted, IBinder token) {
+ setUserRestriction(code, restricted, token, /*exceptionPackages*/null);
+ }
+
+ /** @hide */
+ public void setUserRestriction(int code, boolean restricted, IBinder token,
+ String[] exceptionPackages) {
try {
- mService.setUserRestriction(code, restricted, token, mContext.getUserId());
+ mService.setUserRestriction(code, restricted, token, mContext.getUserId(),
+ exceptionPackages);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index b13be97..3a31b37 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -45,6 +45,6 @@
void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages);
void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle);
- void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle);
+ void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, in String[] exceptionPackages);
void removeUser(int userHandle);
}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 32f2d59..bfe9e8e 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -25,6 +25,7 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -57,6 +58,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -107,8 +109,21 @@
private final SparseArray<UidState> mUidStates = new SparseArray<>();
- /** These are app op restrictions imposed per user from various parties */
- private final ArrayMap<IBinder, SparseArray<boolean[]>> mOpUserRestrictions = new ArrayMap<>();
+ /*
+ * These are app op restrictions imposed per user from various parties.
+ *
+ * This is organized as follows:
+ *
+ * ArrayMap w/ mapping:
+ * IBinder (for client imposing restriction) --> SparseArray w/ mapping:
+ * User handle --> Pair containing:
+ * - Array w/ index = AppOp code, value = restricted status boolean
+ * - SparseArray w/ mapping:
+ * AppOp code --> Set of packages that are not restricted for this code
+ *
+ */
+ private final ArrayMap<IBinder, SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>>
+ mOpUserRestrictions = new ArrayMap<>();
private static final class UidState {
public final int uid;
@@ -1267,11 +1282,35 @@
private boolean isOpRestricted(int uid, int code, String packageName) {
int userHandle = UserHandle.getUserId(uid);
final int restrictionSetCount = mOpUserRestrictions.size();
+
for (int i = 0; i < restrictionSetCount; i++) {
- SparseArray<boolean[]> perUserRestrictions = mOpUserRestrictions.valueAt(i);
- boolean[] opRestrictions = perUserRestrictions.get(userHandle);
- if (opRestrictions != null && opRestrictions[code]) {
+ // For each client, check that the given op is not restricted, or that the given
+ // package is exempt from the restriction.
+
+ SparseArray<Pair<boolean[],SparseArray<ArraySet<String>>>> perUserRestrictions =
+ mOpUserRestrictions.valueAt(i);
+
+ Pair<boolean[],SparseArray<ArraySet<String>>> restrictions =
+ perUserRestrictions.get(userHandle);
+ if (restrictions == null) {
+ continue; // No restrictions set by this client
+ }
+
+ boolean[] opRestrictions = restrictions.first;
+ SparseArray<ArraySet<String>> opExceptions = restrictions.second;
+
+ if (opRestrictions == null) {
+ continue; // No restrictions set by this client
+ }
+
+ if (opRestrictions[code]) {
+ if (opExceptions != null && opExceptions.get(code) != null &&
+ opExceptions.get(code).contains(packageName)) {
+ continue; // AppOps code is restricted, but this package is exempt
+ }
+
if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
+ // If we are the system, bypass user restrictions for certain codes
synchronized (this) {
Ops ops = getOpsLocked(uid, packageName, true);
if ((ops != null) && ops.isPrivileged) {
@@ -1279,6 +1318,7 @@
}
}
}
+
return true;
}
}
@@ -2069,7 +2109,8 @@
}
@Override
- public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle) {
+ public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+ String[] exceptionPackages) {
if (Binder.getCallingPid() != Process.myPid()) {
mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
@@ -2085,12 +2126,37 @@
}
verifyIncomingOp(code);
Preconditions.checkNotNull(token);
- setUserRestrictionNoCheck(code, restricted, token, userHandle);
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, exceptionPackages);
}
private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
int userHandle) {
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, /*exceptionPackages*/null);
+ }
+
+ private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+ int userHandle, String[] exceptionPackages) {
+
final boolean[] opRestrictions = getOrCreateUserRestrictionsForToken(token, userHandle);
+
+ if (restricted) {
+ final SparseArray<ArraySet<String>> opExceptions =
+ getUserPackageExemptionsForToken(token, userHandle);
+
+ // If exceptionPackages is not null, update the exception packages for this AppOps code
+ ArraySet<String> exceptions = opExceptions.get(code);
+ if (exceptionPackages != null) {
+ if (exceptions == null) {
+ exceptions = new ArraySet<>(exceptionPackages.length);
+ opExceptions.put(code, exceptions);
+ } else {
+ exceptions.clear();
+ }
+
+ exceptions.addAll(Arrays.asList(exceptionPackages));
+ }
+ }
+
if (opRestrictions[code] == restricted) {
return;
}
@@ -2132,7 +2198,8 @@
checkSystemUid("removeUser");
final int tokenCount = mOpUserRestrictions.size();
for (int i = tokenCount - 1; i >= 0; i--) {
- SparseArray<boolean[]> opRestrictions = mOpUserRestrictions.valueAt(i);
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> opRestrictions =
+ mOpUserRestrictions.valueAt(i);
if (opRestrictions != null) {
opRestrictions.remove(userHandle);
if (opRestrictions.size() <= 0) {
@@ -2144,15 +2211,23 @@
private void pruneUserRestrictionsForToken(IBinder token, int userHandle) {
- SparseArray<boolean[]> perTokenRestrictions = mOpUserRestrictions.get(token);
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+ mOpUserRestrictions.get(token);
if (perTokenRestrictions != null) {
- final boolean[] opRestrictions = perTokenRestrictions.get(userHandle);
- if (opRestrictions != null) {
- for (boolean restriction : opRestrictions) {
- if (restriction) {
- return;
+ final Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+ perTokenRestrictions.get(userHandle);
+
+ if (restrictions != null) {
+ final boolean[] opRestrictions = restrictions.first;
+ if (opRestrictions != null) {
+ for (boolean restriction : opRestrictions) {
+ if (restriction) {
+ return;
+ }
}
}
+
+ // No restrictions set for this client
perTokenRestrictions.remove(userHandle);
if (perTokenRestrictions.size() <= 0) {
mOpUserRestrictions.remove(token);
@@ -2161,18 +2236,61 @@
}
}
+ /**
+ * Get or create the user restrictions array for a given client if it doesn't already exist.
+ *
+ * @param token the binder client creating the restriction.
+ * @param userHandle the user handle to create a restriction for.
+ *
+ * @return the array of restriction states for each AppOps code.
+ */
private boolean[] getOrCreateUserRestrictionsForToken(IBinder token, int userHandle) {
- SparseArray<boolean[]> perTokenRestrictions = mOpUserRestrictions.get(token);
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+ mOpUserRestrictions.get(token);
+
if (perTokenRestrictions == null) {
- perTokenRestrictions = new SparseArray<>();
+ perTokenRestrictions =
+ new SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>();
mOpUserRestrictions.put(token, perTokenRestrictions);
}
- boolean[] opRestrictions = perTokenRestrictions.get(userHandle);
- if (opRestrictions == null) {
- opRestrictions = new boolean[AppOpsManager._NUM_OP];
- perTokenRestrictions.put(userHandle, opRestrictions);
+
+ Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+ perTokenRestrictions.get(userHandle);
+
+ if (restrictions == null) {
+ restrictions = new Pair<boolean[], SparseArray<ArraySet<String>>>(
+ new boolean[AppOpsManager._NUM_OP], new SparseArray<ArraySet<String>>());
+ perTokenRestrictions.put(userHandle, restrictions);
}
- return opRestrictions;
+
+ return restrictions.first;
+ }
+
+ /**
+ * Get the per-package exemptions for each AppOps code for a given client and userHandle.
+ *
+ * @param token the binder client to get the exemptions for.
+ * @param userHandle the user handle to get the exemptions for.
+ *
+ * @return a mapping from the AppOps code to a set of packages exempt for that code.
+ */
+ private SparseArray<ArraySet<String>> getUserPackageExemptionsForToken(IBinder token,
+ int userHandle) {
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+ mOpUserRestrictions.get(token);
+
+ if (perTokenRestrictions == null) {
+ return null; // Don't create user restrictions accidentally
+ }
+
+ Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+ perTokenRestrictions.get(userHandle);
+
+ if (restrictions == null) {
+ return null; // Don't create user restrictions accidentally
+ }
+
+ return restrictions.second;
}
private void checkSystemUid(String function) {
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index f5914faf..d0ee6e0 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -201,13 +201,16 @@
}
}
- private void updateOverlayStateLocked() {
+ private void updateOverlayStateLocked(ComponentName exemptedComponent) {
final long identity = Binder.clearCallingIdentity();
try {
AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
if (appOpsManager != null) {
+ String[] exemptions = (exemptedComponent == null) ? new String[0] :
+ new String[] { exemptedComponent.getPackageName() };
+
appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
- mVrModeEnabled, mOverlayToken);
+ mVrModeEnabled, mOverlayToken, exemptions);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -230,12 +233,12 @@
private boolean updateCurrentVrServiceLocked(boolean enabled,
@NonNull ComponentName component, int userId) {
- // Always send mode change events.
- changeVrModeLocked(enabled);
-
boolean validUserComponent = (mComponentObserver.isValid(component, userId) ==
EnabledComponentsObserver.NO_ERROR);
+ // Always send mode change events.
+ changeVrModeLocked(enabled, (enabled && validUserComponent) ? component : null);
+
if (!enabled || !validUserComponent) {
// Unbind whatever is running
if (mCurrentVrService != null) {
@@ -275,8 +278,9 @@
* Note: Must be called while holding {@code mLock}.
*
* @param enabled new state of the VR mode.
+ * @param exemptedComponent a component to exempt from AppOps restrictions for overlays.
*/
- private void changeVrModeLocked(boolean enabled) {
+ private void changeVrModeLocked(boolean enabled, ComponentName exemptedComponent) {
if (mVrModeEnabled != enabled) {
mVrModeEnabled = enabled;
@@ -284,7 +288,7 @@
Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
setVrModeNative(mVrModeEnabled);
- updateOverlayStateLocked();
+ updateOverlayStateLocked(exemptedComponent);
onVrModeChangedLocked();
}
}