Merge "Extract appop restrictions"

This commit is contained in:
Jay Sullivan
2022-09-27 05:30:18 +00:00
committed by Android (Google) Code Review
6 changed files with 916 additions and 257 deletions

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2022 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 com.android.server.appop;
import android.os.PackageTagsList;
import java.io.PrintWriter;
/**
* Legacy implementation for AppOpsService's app-op restrictions (global and user)
* storage and access.
*/
public interface AppOpsRestrictions {
/**
* Set or clear a global app-op restriction for the given {@code clientToken}.
*
* @param clientToken A token identifying the client this restriction applies to.
* @param code The app-op opCode to set (or clear) a restriction for.
* @param restricted {@code true} to restrict this app-op code, or {@code false} to clear an
* existing restriction.
* @return {@code true} if any restriction state was modified as a result of this operation
*/
boolean setGlobalRestriction(Object clientToken, int code, boolean restricted);
/**
* Get the state of a global app-op restriction for the given {@code clientToken}.
*
* @param clientToken A token identifying the client to get the restriction state of.
* @param code The app-op code to get the restriction state of.
* @return the restriction state
*/
boolean getGlobalRestriction(Object clientToken, int code);
/**
* Returns {@code true} if *any* global app-op restrictions are currently set for the given
* {@code clientToken}.
*
* @param clientToken A token identifying the client to check restrictions for.
* @return {@code true} if any restrictions are set
*/
boolean hasGlobalRestrictions(Object clientToken);
/**
* Clear *all* global app-op restrictions for the given {@code clientToken}.
*
* @param clientToken A token identifying the client to clear restrictions from.
* @return {@code true} if any restriction state was modified as a result of this operation
*/
boolean clearGlobalRestrictions(Object clientToken);
/**
* Set or clear a user app-op restriction for the given {@code clientToken} and {@code userId}.
*
* @param clientToken A token identifying the client this restriction applies to.
* @param code The app-op code to set (or clear) a restriction for.
* @param restricted {@code true} to restrict this app-op code, or {@code false} to
* remove any existing restriction.
* @param excludedPackageTags A list of packages and associated attribution tags to exclude
* from this restriction. Or, if {@code null}, removes any
* exclusions from this restriction.
* @return {@code true} if any restriction state was modified as a result of this operation
*/
boolean setUserRestriction(Object clientToken, int userId, int code, boolean restricted,
PackageTagsList excludedPackageTags);
/**
* Get the state of a user app-op restriction for the given {@code clientToken} and {@code
* userId}. Or, if the combination of ({{@code clientToken}, {@code userId}, @code
* packageName}, {@code attributionTag}) has been excluded via
* {@link AppOpsRestrictions#setUserRestriction}, always returns {@code false}.
*
* @param clientToken A token identifying the client this restriction applies to.
* @param userId Which userId this restriction applies to.
* @param code The app-op code to get the restriction state of.
* @param packageName A package name used to check for exclusions.
* @param attributionTag An attribution tag used to check for exclusions.
* @param isCheckOp a flag that, when {@code true}, denotes that exclusions should be
* checked by (packageName) rather than (packageName, attributionTag)
* @return the restriction state
*/
boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
String attributionTag, boolean isCheckOp);
/**
* Returns {@code true} if *any* user app-op restrictions are currently set for the given
* {@code clientToken}.
*
* @param clientToken A token identifying the client to check restrictions for.
* @return {@code true} if any restrictions are set
*/
boolean hasUserRestrictions(Object clientToken);
/**
* Clear *all* user app-op restrictions for the given {@code clientToken}.
*
* @param clientToken A token identifying the client to clear restrictions for.
* @return {@code true} if any restriction state was modified as a result of this operation
*/
boolean clearUserRestrictions(Object clientToken);
/**
* Clear *all* user app-op restrictions for the given {@code clientToken} and {@code userId}.
*
* @param clientToken A token identifying the client to clear restrictions for.
* @param userId Which userId to clear restrictions for.
* @return {@code true} if any restriction state was modified as a result of this operation
*/
boolean clearUserRestrictions(Object clientToken, Integer userId);
/**
* Returns the set of exclusions previously set by
* {@link AppOpsRestrictions#setUserRestriction} for the given {@code clientToken}
* and {@code userId}.
*
* @param clientToken A token identifying the client to get restriction exclusions for.
* @param userId Which userId to get restriction exclusions for
* @return a set of user restriction exclusions
*/
PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId);
/**
* Dump the state of appop restrictions.
*
* @param printWriter writer to dump to.
* @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's
* set to an app-op, only the watchers for that app-op are dumped.
* @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package
* name.
* @param showUserRestrictions include user restriction state in the output
*/
void dumpRestrictions(PrintWriter printWriter, int dumpOp, String dumpPackage,
boolean showUserRestrictions);
}

View File

@@ -0,0 +1,452 @@
/*
* Copyright (C) 2022 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 com.android.server.appop;
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.PackageTagsList;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
/**
* Implementation for AppOpsService's app-op restrictions (global and user) storage and retrieval.
*/
public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
private static final int UID_ANY = -2;
private Context mContext;
private Handler mHandler;
private AppOpsServiceInterface mAppOpsServiceInterface;
// Map from (Object token) to (int code) to (boolean restricted)
private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
// Map from (Object token) to (int userId) to (int code) to (boolean restricted)
private final ArrayMap<Object, SparseArray<SparseBooleanArray>> mUserRestrictions =
new ArrayMap<>();
// Map from (Object token) to (int userId) to (PackageTagsList packageTagsList)
private final ArrayMap<Object, SparseArray<PackageTagsList>>
mUserRestrictionExcludedPackageTags = new ArrayMap<>();
public AppOpsRestrictionsImpl(Context context, Handler handler,
AppOpsServiceInterface appOpsServiceInterface) {
mContext = context;
mHandler = handler;
mAppOpsServiceInterface = appOpsServiceInterface;
}
@Override
public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
if (restricted) {
if (!mGlobalRestrictions.containsKey(clientToken)) {
mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
}
SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
Objects.requireNonNull(restrictedCodes);
boolean changed = !restrictedCodes.get(code);
restrictedCodes.put(code, true);
return changed;
} else {
SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
if (restrictedCodes == null) {
return false;
}
boolean changed = restrictedCodes.get(code);
restrictedCodes.delete(code);
if (restrictedCodes.size() == 0) {
mGlobalRestrictions.remove(clientToken);
}
return changed;
}
}
@Override
public boolean getGlobalRestriction(Object clientToken, int code) {
SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
if (restrictedCodes == null) {
return false;
}
return restrictedCodes.get(code);
}
@Override
public boolean hasGlobalRestrictions(Object clientToken) {
return mGlobalRestrictions.containsKey(clientToken);
}
@Override
public boolean clearGlobalRestrictions(Object clientToken) {
return mGlobalRestrictions.remove(clientToken) != null;
}
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS
})
@Override
public boolean setUserRestriction(Object clientToken, int userId, int code,
boolean restricted,
PackageTagsList excludedPackageTags) {
int[] userIds = resolveUserId(userId);
boolean changed = false;
for (int i = 0; i < userIds.length; i++) {
changed |= putUserRestriction(clientToken, userIds[i], code, restricted);
changed |= putUserRestrictionExclusions(clientToken, userIds[i],
excludedPackageTags);
}
return changed;
}
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS
})
private int[] resolveUserId(int userId) {
int[] userIds;
if (userId == UserHandle.USER_ALL) {
// TODO(b/162888972): this call is returning all users, not just live ones - we
// need to either fix the method called, or rename the variable
List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
userIds = new int[liveUsers.size()];
for (int i = 0; i < liveUsers.size(); i++) {
userIds[i] = liveUsers.get(i).id;
}
} else {
userIds = new int[]{userId};
}
return userIds;
}
@Override
public boolean hasUserRestrictions(Object clientToken) {
return mUserRestrictions.containsKey(clientToken);
}
private boolean getUserRestriction(Object clientToken, int userId, int code) {
SparseArray<SparseBooleanArray> userIdRestrictedCodes =
mUserRestrictions.get(clientToken);
if (userIdRestrictedCodes == null) {
return false;
}
SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
if (restrictedCodes == null) {
return false;
}
return restrictedCodes.get(code);
}
@Override
public boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
String attributionTag, boolean isCheckOp) {
boolean restricted = getUserRestriction(clientToken, userId, code);
if (!restricted) {
return false;
}
PackageTagsList perUserExclusions = getUserRestrictionExclusions(clientToken, userId);
if (perUserExclusions == null) {
return true;
}
// TODO (b/240617242) add overload for checkOp to support attribution tags
if (isCheckOp) {
return !perUserExclusions.includes(packageName);
}
return !perUserExclusions.contains(packageName, attributionTag);
}
@Override
public boolean clearUserRestrictions(Object clientToken) {
boolean changed = false;
SparseBooleanArray allUserRestrictedCodes = collectAllUserRestrictedCodes(clientToken);
changed |= mUserRestrictions.remove(clientToken) != null;
changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
notifyAllUserRestrictions(allUserRestrictedCodes);
return changed;
}
private SparseBooleanArray collectAllUserRestrictedCodes(Object clientToken) {
SparseBooleanArray allRestrictedCodes = new SparseBooleanArray();
SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(clientToken);
if (userIdRestrictedCodes == null) {
return allRestrictedCodes;
}
int userIdRestrictedCodesSize = userIdRestrictedCodes.size();
for (int i = 0; i < userIdRestrictedCodesSize; i++) {
SparseBooleanArray restrictedCodes = userIdRestrictedCodes.valueAt(i);
int restrictedCodesSize = restrictedCodes.size();
for (int j = 0; j < restrictedCodesSize; j++) {
int code = restrictedCodes.keyAt(j);
allRestrictedCodes.put(code, true);
}
}
return allRestrictedCodes;
}
// TODO: For clearUserRestrictions, we are calling notifyOpChanged from within the
// LegacyAppOpsServiceInterfaceImpl class. But, for all other changes to restrictions, we're
// calling it from within AppOpsService. This is awkward, and we should probably do it one
// way or the other.
private void notifyAllUserRestrictions(SparseBooleanArray allUserRestrictedCodes) {
int restrictedCodesSize = allUserRestrictedCodes.size();
for (int j = 0; j < restrictedCodesSize; j++) {
int code = allUserRestrictedCodes.keyAt(j);
mHandler.post(() -> mAppOpsServiceInterface.notifyWatchersOfChange(code, UID_ANY));
}
}
@Override
public boolean clearUserRestrictions(Object clientToken, Integer userId) {
boolean changed = false;
SparseArray<SparseBooleanArray> userIdRestrictedCodes =
mUserRestrictions.get(clientToken);
if (userIdRestrictedCodes != null) {
changed |= userIdRestrictedCodes.contains(userId);
userIdRestrictedCodes.remove(userId);
if (userIdRestrictedCodes.size() == 0) {
mUserRestrictions.remove(clientToken);
}
}
SparseArray<PackageTagsList> userIdPackageTags =
mUserRestrictionExcludedPackageTags.get(clientToken);
if (userIdPackageTags != null) {
changed |= userIdPackageTags.contains(userId);
userIdPackageTags.remove(userId);
if (userIdPackageTags.size() == 0) {
mUserRestrictionExcludedPackageTags.remove(clientToken);
}
}
return changed;
}
private boolean putUserRestriction(Object token, int userId, int code, boolean restricted) {
boolean changed = false;
if (restricted) {
if (!mUserRestrictions.containsKey(token)) {
mUserRestrictions.put(token, new SparseArray<>());
}
SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
Objects.requireNonNull(userIdRestrictedCodes);
if (!userIdRestrictedCodes.contains(userId)) {
userIdRestrictedCodes.put(userId, new SparseBooleanArray());
}
SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
changed = !restrictedCodes.get(code);
restrictedCodes.put(code, restricted);
} else {
SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
if (userIdRestrictedCodes == null) {
return false;
}
SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
if (restrictedCodes == null) {
return false;
}
changed = restrictedCodes.get(code);
restrictedCodes.delete(code);
if (restrictedCodes.size() == 0) {
userIdRestrictedCodes.remove(userId);
}
if (userIdRestrictedCodes.size() == 0) {
mUserRestrictions.remove(token);
}
}
return changed;
}
@Override
public PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId) {
SparseArray<PackageTagsList> userIdPackageTags =
mUserRestrictionExcludedPackageTags.get(clientToken);
if (userIdPackageTags == null) {
return null;
}
return userIdPackageTags.get(userId);
}
private boolean putUserRestrictionExclusions(Object token, int userId,
PackageTagsList excludedPackageTags) {
boolean addingExclusions = excludedPackageTags != null && !excludedPackageTags.isEmpty();
if (addingExclusions) {
if (!mUserRestrictionExcludedPackageTags.containsKey(token)) {
mUserRestrictionExcludedPackageTags.put(token, new SparseArray<>());
}
SparseArray<PackageTagsList> userIdExcludedPackageTags =
mUserRestrictionExcludedPackageTags.get(token);
Objects.requireNonNull(userIdExcludedPackageTags);
userIdExcludedPackageTags.put(userId, excludedPackageTags);
return true;
} else {
SparseArray<PackageTagsList> userIdExclusions =
mUserRestrictionExcludedPackageTags.get(token);
if (userIdExclusions == null) {
return false;
}
boolean changed = userIdExclusions.get(userId) != null;
userIdExclusions.remove(userId);
if (userIdExclusions.size() == 0) {
mUserRestrictionExcludedPackageTags.remove(token);
}
return changed;
}
}
@Override
public void dumpRestrictions(PrintWriter pw, int code, String dumpPackage,
boolean showUserRestrictions) {
final int globalRestrictionCount = mGlobalRestrictions.size();
for (int i = 0; i < globalRestrictionCount; i++) {
Object token = mGlobalRestrictions.keyAt(i);
SparseBooleanArray restrictedOps = mGlobalRestrictions.valueAt(i);
pw.println(" Global restrictions for token " + token + ":");
StringBuilder restrictedOpsValue = new StringBuilder();
restrictedOpsValue.append("[");
final int restrictedOpCount = restrictedOps.size();
for (int j = 0; j < restrictedOpCount; j++) {
if (restrictedOpsValue.length() > 1) {
restrictedOpsValue.append(", ");
}
restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.keyAt(j)));
}
restrictedOpsValue.append("]");
pw.println(" Restricted ops: " + restrictedOpsValue);
}
if (!showUserRestrictions) {
return;
}
final int userRestrictionCount = mUserRestrictions.size();
for (int i = 0; i < userRestrictionCount; i++) {
Object token = mUserRestrictions.keyAt(i);
SparseArray<SparseBooleanArray> perUserRestrictions = mUserRestrictions.get(token);
SparseArray<PackageTagsList> perUserExcludedPackageTags =
mUserRestrictionExcludedPackageTags.get(token);
boolean printedTokenHeader = false;
final int restrictionCount = perUserRestrictions != null
? perUserRestrictions.size() : 0;
if (restrictionCount > 0 && dumpPackage == null) {
boolean printedOpsHeader = false;
for (int j = 0; j < restrictionCount; j++) {
int userId = perUserRestrictions.keyAt(j);
SparseBooleanArray restrictedOps = perUserRestrictions.valueAt(j);
if (restrictedOps == null) {
continue;
}
if (code >= 0 && !restrictedOps.get(code)) {
continue;
}
if (!printedTokenHeader) {
pw.println(" User restrictions for token " + token + ":");
printedTokenHeader = true;
}
if (!printedOpsHeader) {
pw.println(" Restricted ops:");
printedOpsHeader = true;
}
StringBuilder restrictedOpsValue = new StringBuilder();
restrictedOpsValue.append("[");
final int restrictedOpCount = restrictedOps.size();
for (int k = 0; k < restrictedOpCount; k++) {
int restrictedOp = restrictedOps.keyAt(k);
if (restrictedOpsValue.length() > 1) {
restrictedOpsValue.append(", ");
}
restrictedOpsValue.append(AppOpsManager.opToName(restrictedOp));
}
restrictedOpsValue.append("]");
pw.print(" ");
pw.print("user: ");
pw.print(userId);
pw.print(" restricted ops: ");
pw.println(restrictedOpsValue);
}
}
final int excludedPackageCount = perUserExcludedPackageTags != null
? perUserExcludedPackageTags.size() : 0;
if (excludedPackageCount > 0 && code < 0) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.increaseIndent();
boolean printedPackagesHeader = false;
for (int j = 0; j < excludedPackageCount; j++) {
int userId = perUserExcludedPackageTags.keyAt(j);
PackageTagsList packageNames =
perUserExcludedPackageTags.valueAt(j);
if (packageNames == null) {
continue;
}
boolean hasPackage;
if (dumpPackage != null) {
hasPackage = packageNames.includes(dumpPackage);
} else {
hasPackage = true;
}
if (!hasPackage) {
continue;
}
if (!printedTokenHeader) {
ipw.println("User restrictions for token " + token + ":");
printedTokenHeader = true;
}
ipw.increaseIndent();
if (!printedPackagesHeader) {
ipw.println("Excluded packages:");
printedPackagesHeader = true;
}
ipw.increaseIndent();
ipw.print("user: ");
ipw.print(userId);
ipw.println(" packages: ");
ipw.increaseIndent();
packageNames.dump(ipw);
ipw.decreaseIndent();
ipw.decreaseIndent();
ipw.decreaseIndent();
}
ipw.decreaseIndent();
}
}
}
}

View File

@@ -97,7 +97,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
import android.net.Uri;
@@ -118,14 +117,12 @@ import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManagerInternal;
import android.permission.PermissionManager;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.KeyValueListParser;
import android.util.Pair;
import android.util.Slog;
@@ -367,6 +364,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
/** Interface for app-op modes.*/
@VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
/** Interface for app-op restrictions.*/
@VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
private AppOpsUidStateTracker mUidStateTracker;
/** Hands the definition of foreground and uid states */
@@ -926,6 +926,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
}
mAppOpsServiceInterface =
new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
mAppOpsServiceInterface);
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mFile = new AtomicFile(storagePath, "appops");
@@ -5348,124 +5350,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
pw.println();
}
final int globalRestrictionCount = mOpGlobalRestrictions.size();
for (int i = 0; i < globalRestrictionCount; i++) {
IBinder token = mOpGlobalRestrictions.keyAt(i);
ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
ArraySet<Integer> restrictedOps = restrictionState.mRestrictedOps;
pw.println(" Global restrictions for token " + token + ":");
StringBuilder restrictedOpsValue = new StringBuilder();
restrictedOpsValue.append("[");
final int restrictedOpCount = restrictedOps.size();
for (int j = 0; j < restrictedOpCount; j++) {
if (restrictedOpsValue.length() > 1) {
restrictedOpsValue.append(", ");
}
restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.valueAt(j)));
}
restrictedOpsValue.append("]");
pw.println(" Restricted ops: " + restrictedOpsValue);
}
final int userRestrictionCount = mOpUserRestrictions.size();
for (int i = 0; i < userRestrictionCount; i++) {
IBinder token = mOpUserRestrictions.keyAt(i);
ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
boolean printedTokenHeader = false;
if (dumpMode >= 0 || dumpWatchers || dumpHistory) {
continue;
}
final int restrictionCount = restrictionState.perUserRestrictions != null
? restrictionState.perUserRestrictions.size() : 0;
if (restrictionCount > 0 && dumpPackage == null) {
boolean printedOpsHeader = false;
for (int j = 0; j < restrictionCount; j++) {
int userId = restrictionState.perUserRestrictions.keyAt(j);
boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
if (restrictedOps == null) {
continue;
}
if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
|| !restrictedOps[dumpOp])) {
continue;
}
if (!printedTokenHeader) {
pw.println(" User restrictions for token " + token + ":");
printedTokenHeader = true;
}
if (!printedOpsHeader) {
pw.println(" Restricted ops:");
printedOpsHeader = true;
}
StringBuilder restrictedOpsValue = new StringBuilder();
restrictedOpsValue.append("[");
final int restrictedOpCount = restrictedOps.length;
for (int k = 0; k < restrictedOpCount; k++) {
if (restrictedOps[k]) {
if (restrictedOpsValue.length() > 1) {
restrictedOpsValue.append(", ");
}
restrictedOpsValue.append(AppOpsManager.opToName(k));
}
}
restrictedOpsValue.append("]");
pw.print(" "); pw.print("user: "); pw.print(userId);
pw.print(" restricted ops: "); pw.println(restrictedOpsValue);
}
}
final int excludedPackageCount = restrictionState.perUserExcludedPackageTags != null
? restrictionState.perUserExcludedPackageTags.size() : 0;
if (excludedPackageCount > 0 && dumpOp < 0) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.increaseIndent();
boolean printedPackagesHeader = false;
for (int j = 0; j < excludedPackageCount; j++) {
int userId = restrictionState.perUserExcludedPackageTags.keyAt(j);
PackageTagsList packageNames =
restrictionState.perUserExcludedPackageTags.valueAt(j);
if (packageNames == null) {
continue;
}
boolean hasPackage;
if (dumpPackage != null) {
hasPackage = packageNames.includes(dumpPackage);
} else {
hasPackage = true;
}
if (!hasPackage) {
continue;
}
if (!printedTokenHeader) {
ipw.println("User restrictions for token " + token + ":");
printedTokenHeader = true;
}
ipw.increaseIndent();
if (!printedPackagesHeader) {
ipw.println("Excluded packages:");
printedPackagesHeader = true;
}
ipw.increaseIndent();
ipw.print("user: ");
ipw.print(userId);
ipw.println(" packages: ");
ipw.increaseIndent();
packageNames.dump(ipw);
ipw.decreaseIndent();
ipw.decreaseIndent();
ipw.decreaseIndent();
}
ipw.decreaseIndent();
}
}
boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
if (!dumpHistory && !dumpWatchers) {
pw.println();
@@ -6085,8 +5971,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
private final class ClientUserRestrictionState implements DeathRecipient {
private final IBinder token;
SparseArray<boolean[]> perUserRestrictions;
SparseArray<PackageTagsList> perUserExcludedPackageTags;
ClientUserRestrictionState(IBinder token)
throws RemoteException {
@@ -6096,134 +5980,29 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
public boolean setRestriction(int code, boolean restricted,
PackageTagsList excludedPackageTags, int userId) {
boolean changed = false;
if (perUserRestrictions == null && restricted) {
perUserRestrictions = new SparseArray<>();
}
int[] users;
if (userId == UserHandle.USER_ALL) {
// TODO(b/162888972): this call is returning all users, not just live ones - we
// need to either fix the method called, or rename the variable
List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
users = new int[liveUsers.size()];
for (int i = 0; i < liveUsers.size(); i++) {
users[i] = liveUsers.get(i).id;
}
} else {
users = new int[]{userId};
}
if (perUserRestrictions != null) {
int numUsers = users.length;
for (int i = 0; i < numUsers; i++) {
int thisUserId = users[i];
boolean[] userRestrictions = perUserRestrictions.get(thisUserId);
if (userRestrictions == null && restricted) {
userRestrictions = new boolean[AppOpsManager._NUM_OP];
perUserRestrictions.put(thisUserId, userRestrictions);
}
if (userRestrictions != null && userRestrictions[code] != restricted) {
userRestrictions[code] = restricted;
if (!restricted && isDefault(userRestrictions)) {
perUserRestrictions.remove(thisUserId);
userRestrictions = null;
}
changed = true;
}
if (userRestrictions != null) {
final boolean noExcludedPackages =
excludedPackageTags == null || excludedPackageTags.isEmpty();
if (perUserExcludedPackageTags == null && !noExcludedPackages) {
perUserExcludedPackageTags = new SparseArray<>();
}
if (perUserExcludedPackageTags != null) {
if (noExcludedPackages) {
perUserExcludedPackageTags.remove(thisUserId);
if (perUserExcludedPackageTags.size() <= 0) {
perUserExcludedPackageTags = null;
}
} else {
perUserExcludedPackageTags.put(thisUserId, excludedPackageTags);
}
changed = true;
}
}
}
}
return changed;
return mAppOpsRestrictions.setUserRestriction(token, userId, code,
restricted, excludedPackageTags);
}
public boolean hasRestriction(int restriction, String packageName, String attributionTag,
public boolean hasRestriction(int code, String packageName, String attributionTag,
int userId, boolean isCheckOp) {
if (perUserRestrictions == null) {
return false;
}
boolean[] restrictions = perUserRestrictions.get(userId);
if (restrictions == null) {
return false;
}
if (!restrictions[restriction]) {
return false;
}
if (perUserExcludedPackageTags == null) {
return true;
}
PackageTagsList perUserExclusions = perUserExcludedPackageTags.get(userId);
if (perUserExclusions == null) {
return true;
}
// TODO (b/240617242) add overload for checkOp to support attribution tags
if (isCheckOp) {
return !perUserExclusions.includes(packageName);
}
return !perUserExclusions.contains(packageName, attributionTag);
return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
attributionTag, isCheckOp);
}
public void removeUser(int userId) {
if (perUserExcludedPackageTags != null) {
perUserExcludedPackageTags.remove(userId);
if (perUserExcludedPackageTags.size() <= 0) {
perUserExcludedPackageTags = null;
}
}
if (perUserRestrictions != null) {
perUserRestrictions.remove(userId);
if (perUserRestrictions.size() <= 0) {
perUserRestrictions = null;
}
}
mAppOpsRestrictions.clearUserRestrictions(token, userId);
}
public boolean isDefault() {
return perUserRestrictions == null || perUserRestrictions.size() <= 0;
return !mAppOpsRestrictions.hasUserRestrictions(token);
}
@Override
public void binderDied() {
synchronized (AppOpsService.this) {
mAppOpsRestrictions.clearUserRestrictions(token);
mOpUserRestrictions.remove(token);
if (perUserRestrictions == null) {
return;
}
final int userCount = perUserRestrictions.size();
for (int i = 0; i < userCount; i++) {
final boolean[] restrictions = perUserRestrictions.valueAt(i);
final int restrictionCount = restrictions.length;
for (int j = 0; j < restrictionCount; j++) {
if (restrictions[j]) {
final int changedCode = j;
mHandler.post(() -> notifyWatchersOfChange(changedCode, UID_ANY));
}
}
}
destroy();
}
}
@@ -6231,23 +6010,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
public void destroy() {
token.unlinkToDeath(this, 0);
}
private boolean isDefault(boolean[] array) {
if (ArrayUtils.isEmpty(array)) {
return true;
}
for (boolean value : array) {
if (value) {
return false;
}
}
return true;
}
}
private final class ClientGlobalRestrictionState implements DeathRecipient {
final IBinder mToken;
final ArraySet<Integer> mRestrictedOps = new ArraySet<>();
ClientGlobalRestrictionState(IBinder token)
throws RemoteException {
@@ -6256,23 +6022,21 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
}
boolean setRestriction(int code, boolean restricted) {
if (restricted) {
return mRestrictedOps.add(code);
} else {
return mRestrictedOps.remove(code);
}
return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
}
boolean hasRestriction(int code) {
return mRestrictedOps.contains(code);
return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
}
boolean isDefault() {
return mRestrictedOps.isEmpty();
return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
}
@Override
public void binderDied() {
mAppOpsRestrictions.clearGlobalRestrictions(mToken);
mOpGlobalRestrictions.remove(mToken);
destroy();
}

View File

@@ -145,6 +145,14 @@ public interface AppOpsServiceInterface {
*/
ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
/**
* Temporary API which will be removed once we can safely untangle the methods that use this.
* Notify that the app-op's mode is changed by triggering the change listener.
* @param op App-op whose mode has changed
* @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
*/
void notifyWatchersOfChange(int op, int uid);
/**
* Temporary API which will be removed once we can safely untangle the methods that use this.
* Notify that the app-op's mode is changed by triggering the change listener.
@@ -198,5 +206,4 @@ public interface AppOpsServiceInterface {
* @param printWriter writer to dump to.
*/
boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
}

View File

@@ -332,6 +332,18 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface
}
}
@Override
public void notifyWatchersOfChange(int code, int uid) {
ArraySet<OnOpModeChangedListener> listenerSet = getOpModeChangedListeners(code);
if (listenerSet == null) {
return;
}
for (int i = 0; i < listenerSet.size(); i++) {
final OnOpModeChangedListener listener = listenerSet.valueAt(i);
notifyOpChanged(listener, code, uid, null);
}
}
@Override
public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
int uid, @Nullable String packageName) {

View File

@@ -0,0 +1,277 @@
/*
* Copyright (C) 2022 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 com.android.server.appop;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.os.Handler;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.quality.Strictness;
public class AppOpsLegacyRestrictionsTest {
private static final int UID_ANY = -2;
final Object mClientToken = new Object();
final int mUserId1 = 65001;
final int mUserId2 = 65002;
final int mOpCode1 = OP_COARSE_LOCATION;
final int mOpCode2 = OP_FINE_LOCATION;
final String mPackageName = "com.example.test";
final String mAttributionTag = "test-attribution-tag";
StaticMockitoSession mSession;
@Mock
AppOpsService.Constants mConstants;
@Mock
Context mContext;
@Mock
Handler mHandler;
@Mock
AppOpsServiceInterface mLegacyAppOpsService;
AppOpsRestrictions mAppOpsRestrictions;
@Before
public void setUp() {
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
.startMocking();
mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
Mockito.when(mHandler.post(Mockito.any(Runnable.class))).then(inv -> {
Runnable r = inv.getArgument(0);
r.run();
return true;
});
mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler, mLegacyAppOpsService);
}
@After
public void tearDown() {
mSession.finishMocking();
}
@Test
public void testSetAndGetSingleGlobalRestriction() {
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
// Act: add a restriction
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
// Act: add same restriction again (expect false; should be no-op)
assertEquals(false, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
assertEquals(true, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
// Act: remove the restriction
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
// Act: remove same restriction again (expect false; should be no-op)
assertEquals(false,
mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
}
@Test
public void testSetAndGetDoubleGlobalRestriction() {
// Act: add opCode1 restriction
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
// Act: add opCode2 restriction
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
// Act: remove opCode1 restriction
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
// Act: remove opCode2 restriction
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, false));
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
}
@Test
public void testClearGlobalRestrictions() {
// Act: clear (should be no-op)
assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
// Act: add opCodes
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
// Act: clear
assertEquals(true, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
// Act: clear (should be no-op)
assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
}
@Test
public void testSetAndGetSingleUserRestriction() {
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
// Act: add a restriction
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, true, null));
// Act: add the restriction again (should be no-op)
assertEquals(false, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, true, null));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
// Act: remove the restriction
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, false, null));
// Act: remove the restriction again (should be no-op)
assertEquals(false, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, false, null));
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
}
@Test
public void testSetAndGetDoubleUserRestriction() {
// Act: add opCode1 restriction
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, true, null));
// Act: add opCode2 restriction
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode2, true, null));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
// Act: remove opCode1 restriction
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, false, null));
// Verify: opCode1 is removed but not opCode22
assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
// Act: remove opCode2 restriction
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode2, false, null));
// Verify: empty
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
}
@Test
public void testClearUserRestrictionsAllUsers() {
// Act: clear (should be no-op)
assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
// Act: add restrictions
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, true, null));
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode2, true, null));
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId2, mOpCode1, true, null));
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId2, mOpCode2, true, null));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
// Act: clear all user restrictions
assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
}
@Test
public void testClearUserRestrictionsSpecificUsers() {
// Act: clear (should be no-op)
assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
// Act: add restrictions
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode1, true, null));
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId1, mOpCode2, true, null));
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId2, mOpCode1, true, null));
assertEquals(true, mAppOpsRestrictions.setUserRestriction(
mClientToken, mUserId2, mOpCode2, true, null));
// Verify: not empty
assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
// Act: clear userId1
assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
// Act: clear userId1 again (should be no-op)
assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
// Verify: userId1 is removed but not userId2
assertEquals(false, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
assertEquals(true, mAppOpsRestrictions.getUserRestriction(
mClientToken, mUserId2, mOpCode2, mPackageName, mAttributionTag, false));
// Act: clear userId2
assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
// Act: clear userId2 again (should be no-op)
assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
// Verify: empty
assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
}
@Test
public void testNotify() {
mAppOpsRestrictions.setUserRestriction(mClientToken, mUserId1, mOpCode1, true, null);
mAppOpsRestrictions.clearUserRestrictions(mClientToken);
Mockito.verify(mLegacyAppOpsService, Mockito.times(1))
.notifyWatchersOfChange(mOpCode1, UID_ANY);
}
}