Merge "Extract appop restrictions"
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user