Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 525739fb authored by Jay Sullivan's avatar Jay Sullivan Committed by Android (Google) Code Review
Browse files

Merge "Extract appop restrictions"

parents e01d4ecc 7699ecdf
Loading
Loading
Loading
Loading
+147 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+452 −0

File added.

Preview size limit exceeded, changes collapsed.

+20 −256
Original line number Diff line number Diff line
@@ -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};
            return mAppOpsRestrictions.setUserRestriction(token, userId, code,
                    restricted, excludedPackageTags);
        }

            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;
        }

        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();
        }

+8 −1
Original line number Diff line number Diff line
@@ -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);

}
+12 −0
Original line number Diff line number Diff line
@@ -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) {
Loading