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

Commit 4f968024 authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by android-build-merger
Browse files

Merge "Ensure app op restrictions reset when the app that set them dies." into nyc-dev

am: 008d73bd

* commit '008d73bd':
  Ensure app op restrictions reset when the app that set them dies.

Change-Id: I177256a7f65b3f5c98e5ddc5cf57a7cb47c965ab
parents 14ecfb69 008d73bd
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -161,6 +161,13 @@ public class ArrayUtils {
        return array == null || array.length == 0;
    }

    /**
     * Checks if given array is null or has zero elements.
     */
    public static boolean isEmpty(@Nullable boolean[] array) {
        return array == null || array.length == 0;
    }

    /**
     * Checks that value is present as at least one of the elements of the array.
     * @param array the array to check in
+160 −188
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -57,7 +58,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -110,21 +110,8 @@ public class AppOpsService extends IAppOpsService.Stub {

    /*
     * These are app op restrictions imposed per user from various parties.
     *
     * This is organized as follows:
     *
     * ArrayMap w/ mapping:
     *  IBinder (for client imposing restriction) --> SparseArray w/ mapping:
     *    User handle --> Pair containing:
     *       - Array w/ index = AppOp code, value = restricted status boolean
     *       - SparseArray w/ mapping:
     *          AppOp code --> Set of packages that are not restricted for this code
     *
     * For efficiency, a core assumption here is that the number of per-package exemptions stored
     * here will be relatively small.  If this changes, this data structure should be revisited.
     */
    private final ArrayMap<IBinder, SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>>
            mOpUserRestrictions = new ArrayMap<>();
    private final ArrayMap<IBinder, ClientRestrictionState> mOpUserRestrictions = new ArrayMap<>();

    private static final class UidState {
        public final int uid;
@@ -1328,33 +1315,9 @@ public class AppOpsService extends IAppOpsService.Stub {
        for (int i = 0; i < restrictionSetCount; i++) {
            // For each client, check that the given op is not restricted, or that the given
            // package is exempt from the restriction.

            SparseArray<Pair<boolean[],SparseArray<ArraySet<String>>>> perUserRestrictions =
                    mOpUserRestrictions.valueAt(i);

            Pair<boolean[],SparseArray<ArraySet<String>>> restrictions =
                    perUserRestrictions.get(userHandle);
            if (restrictions == null) {
                continue; // No restrictions set by this client
            }

            boolean[] opRestrictions = restrictions.first;
            SparseArray<ArraySet<String>> opExceptions = restrictions.second;

            if (opRestrictions == null) {
                continue; // No restrictions set by this client
            }

            if (opRestrictions[code]) {

                if (opExceptions != null) {
                    ArraySet<String> ex = opExceptions.get(code);
                    if (ex != null && ex.contains(packageName)) {
                        continue; // AppOps code is restricted, but this package is exempt
                    }
                }

                if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
            ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
            if (restrictionState.hasRestriction(code, packageName, userHandle)
                    && AppOpsManager.opAllowSystemBypassRestriction(code)) {
                // If we are the system, bypass user restrictions for certain codes
                synchronized (this) {
                    Ops ops = getOpsRawLocked(uid, packageName, true);
@@ -1362,8 +1325,6 @@ public class AppOpsService extends IAppOpsService.Stub {
                        return false;
                    }
                }
                }

                return true;
            }
        }
@@ -2216,12 +2177,11 @@ public class AppOpsService extends IAppOpsService.Stub {
        checkSystemUid("setUserRestrictions");
        Preconditions.checkNotNull(restrictions);
        Preconditions.checkNotNull(token);
        final boolean[] opRestrictions = getOrCreateUserRestrictionsForToken(token, userHandle);
        for (int i = 0; i < opRestrictions.length; ++i) {
        for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
            String restriction = AppOpsManager.opToRestriction(i);
            final boolean restricted = restriction != null
                    && restrictions.getBoolean(restriction, false);
            setUserRestrictionNoCheck(i, restricted, token, userHandle);
            if (restriction != null && restrictions.getBoolean(restriction, false)) {
                setUserRestrictionNoCheck(i, true, token, userHandle, null);
            }
        }
    }

@@ -2246,48 +2206,29 @@ public class AppOpsService extends IAppOpsService.Stub {
        setUserRestrictionNoCheck(code, restricted, token, userHandle, exceptionPackages);
    }

    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
            int userHandle) {
        setUserRestrictionNoCheck(code, restricted, token, userHandle, /*exceptionPackages*/null);
    }

    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
            int userHandle, String[] exceptionPackages) {
        ClientRestrictionState restrictionState = mOpUserRestrictions.get(token);

        final boolean[] opRestrictions = getOrCreateUserRestrictionsForToken(token, userHandle);

        if (restricted) {
            final SparseArray<ArraySet<String>> opExceptions =
                    getUserPackageExemptionsForToken(token, userHandle);

            ArraySet<String> exceptions = opExceptions.get(code);
            if (exceptionPackages != null && exceptionPackages.length > 0) {
                if (exceptions == null) {
                    exceptions = new ArraySet<>(exceptionPackages.length);
                    opExceptions.put(code, exceptions);
                } else {
                    exceptions.clear();
                }

                for (String p : exceptionPackages) {
                    exceptions.add(p);
                }
            } else {
                opExceptions.remove(code);
            }
        }

        if (opRestrictions[code] == restricted) {
        if (restrictionState == null) {
            try {
                restrictionState = new ClientRestrictionState(token);
            } catch (RemoteException e) {
                return;
            }
        opRestrictions[code] = restricted;
        if (!restricted) {
            pruneUserRestrictionsForToken(token, userHandle);
            mOpUserRestrictions.put(token, restrictionState);
        }

        if (restrictionState.setRestriction(code, restricted, exceptionPackages, userHandle)) {
            notifyWatchersOfChange(code);
        }

        if (restrictionState.isDefault()) {
            mOpUserRestrictions.remove(token);
            restrictionState.destroy();
        }
    }

    private void notifyWatchersOfChange(int code) {
        final ArrayList<Callback> clonedCallbacks;
        synchronized (this) {
@@ -2300,7 +2241,7 @@ public class AppOpsService extends IAppOpsService.Stub {

        // There are components watching for mode changes such as window manager
        // and location manager which are in our process. The callbacks in these
        // components may require permissions our remote caller does not have.
        // components may require permissions our remote caller does not have.s
        final long identity = Binder.clearCallingIdentity();
        try {
            final int callbackCount = clonedCallbacks.size();
@@ -2322,138 +2263,169 @@ public class AppOpsService extends IAppOpsService.Stub {
        checkSystemUid("removeUser");
        final int tokenCount = mOpUserRestrictions.size();
        for (int i = tokenCount - 1; i >= 0; i--) {
            SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> opRestrictions =
                    mOpUserRestrictions.valueAt(i);
            if (opRestrictions != null) {
                opRestrictions.remove(userHandle);
                if (opRestrictions.size() <= 0) {
                    mOpUserRestrictions.removeAt(i);
            ClientRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
            opRestrictions.removeUser(userHandle);
        }
    }

    private void checkSystemUid(String function) {
        int uid = Binder.getCallingUid();
        if (uid != Process.SYSTEM_UID) {
            throw new SecurityException(function + " must by called by the system");
        }
    }

    private static String resolvePackageName(int uid, String packageName)  {
        if (uid == 0) {
            return "root";
        } else if (uid == Process.SHELL_UID) {
            return "com.android.shell";
        } else if (uid == Process.SYSTEM_UID && packageName == null) {
            return "android";
        }
        return packageName;
    }

    private void pruneUserRestrictionsForToken(IBinder token, int userHandle) {
        SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
                mOpUserRestrictions.get(token);
        if (perTokenRestrictions != null) {
            final Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
                    perTokenRestrictions.get(userHandle);

            if (restrictions != null) {
                final boolean[] opRestrictions = restrictions.first;
                final SparseArray<ArraySet<String>> opExceptions = restrictions.second;
                boolean stillHasRestrictions = false;
                if (opRestrictions != null) {
                    for (int i = 0; i < opRestrictions.length; i++) {
                        boolean restriction = opRestrictions[i];
                        if (restriction) {
                            stillHasRestrictions = true;
                        } else {
                            opExceptions.remove(i);
    private static String[] getPackagesForUid(int uid) {
        String[] packageNames = null;
        try {
            packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
        } catch (RemoteException e) {
            /* ignore - local call */
        }
        if (packageNames == null) {
            return EmptyArray.STRING;
        }
        return packageNames;
    }

                if (stillHasRestrictions) {
                    return;
    private final class ClientRestrictionState implements DeathRecipient {
        private final IBinder token;
        SparseArray<boolean[]> perUserRestrictions;
        SparseArray<String[]> perUserExcludedPackages;

        public ClientRestrictionState(IBinder token)
                throws RemoteException {
            token.linkToDeath(this, 0);
            this.token = token;
        }

                // No restrictions set for this client
                perTokenRestrictions.remove(userHandle);
                if (perTokenRestrictions.size() <= 0) {
                    mOpUserRestrictions.remove(token);
        public boolean setRestriction(int code, boolean restricted,
                String[] excludedPackages, int userId) {
            boolean changed = false;

            if (perUserRestrictions == null && restricted) {
                perUserRestrictions = new SparseArray<>();
            }

            if (perUserRestrictions != null) {
                boolean[] userRestrictions = perUserRestrictions.get(userId);
                if (userRestrictions == null && restricted) {
                    userRestrictions = new boolean[AppOpsManager._NUM_OP];
                    perUserRestrictions.put(userId, userRestrictions);
                }
                if (userRestrictions != null && userRestrictions[code] != restricted) {
                    userRestrictions[code] = restricted;
                    if (!restricted && isDefault(userRestrictions)) {
                        perUserRestrictions.remove(userId);
                        userRestrictions = null;
                    }
                    changed = true;
                }

    /**
     * Get or create the user restrictions array for a given client if it doesn't already exist.
     *
     * @param token the binder client creating the restriction.
     * @param userHandle the user handle to create a restriction for.
     *
     * @return the array of restriction states for each AppOps code.
     */
    private boolean[] getOrCreateUserRestrictionsForToken(IBinder token, int userHandle) {
        SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
                mOpUserRestrictions.get(token);

        if (perTokenRestrictions == null) {
            perTokenRestrictions =
                    new SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>();
            mOpUserRestrictions.put(token, perTokenRestrictions);
                if (userRestrictions != null) {
                    final boolean noExcludedPackages = ArrayUtils.isEmpty(excludedPackages);
                    if (perUserExcludedPackages == null && !noExcludedPackages) {
                        perUserExcludedPackages = new SparseArray<>();
                    }
                    if (perUserExcludedPackages != null && !Arrays.equals(excludedPackages,
                            perUserExcludedPackages.get(userId))) {
                        if (noExcludedPackages) {
                            perUserExcludedPackages.remove(userId);
                            if (perUserExcludedPackages.size() <= 0) {
                                perUserExcludedPackages = null;
                            }
                        } else {
                            perUserExcludedPackages.put(userId, excludedPackages);
                        }
                        changed = true;
                    }
                }
            }

        Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
                perTokenRestrictions.get(userHandle);
            return changed;
        }

        public boolean hasRestriction(int restriction, String packageName, int userId) {
            if (perUserRestrictions == null) {
                return false;
            }
            boolean[] restrictions = perUserRestrictions.get(userId);
            if (restrictions == null) {
            restrictions = new Pair<boolean[], SparseArray<ArraySet<String>>>(
                    new boolean[AppOpsManager._NUM_OP], new SparseArray<ArraySet<String>>());
            perTokenRestrictions.put(userHandle, restrictions);
                return false;
            }

        return restrictions.first;
            if (!restrictions[restriction]) {
                return false;
            }

    /**
     * Get the per-package exemptions for each AppOps code for a given client and userHandle.
     *
     * @param token the binder client to get the exemptions for.
     * @param userHandle the user handle to get the exemptions for.
     *
     * @return a mapping from the AppOps code to a set of packages exempt for that code.
     */
    private SparseArray<ArraySet<String>> getUserPackageExemptionsForToken(IBinder token,
            int userHandle) {
        SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
                mOpUserRestrictions.get(token);

        if (perTokenRestrictions == null) {
            return null; // Don't create user restrictions accidentally
            if (perUserExcludedPackages == null) {
                return true;
            }
            String[] perUserExclusions = perUserExcludedPackages.get(userId);
            if (perUserExclusions == null) {
                return true;
            }
            return !ArrayUtils.contains(perUserExclusions, packageName);
        }

        Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
                perTokenRestrictions.get(userHandle);

        if (restrictions == null) {
            return null; // Don't create user restrictions accidentally
        public void removeUser(int userId) {
            if (perUserExcludedPackages != null) {
                perUserExcludedPackages.remove(userId);
                if (perUserExcludedPackages.size() <= 0) {
                    perUserExcludedPackages = null;
                }
            }
        }

        return restrictions.second;
        public boolean isDefault() {
            return perUserRestrictions == null || perUserRestrictions.size() <= 0;
        }

    private void checkSystemUid(String function) {
        int uid = Binder.getCallingUid();
        if (uid != Process.SYSTEM_UID) {
            throw new SecurityException(function + " must by called by the system");
        @Override
        public void binderDied() {
            synchronized (AppOpsService.this) {
                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));
                        }

    private static String resolvePackageName(int uid, String packageName)  {
        if (uid == 0) {
            return "root";
        } else if (uid == Process.SHELL_UID) {
            return "com.android.shell";
        } else if (uid == Process.SYSTEM_UID && packageName == null) {
            return "android";
                    }
        return packageName;
                }
                destroy();
            }
        }

    private static String[] getPackagesForUid(int uid) {
        String[] packageNames = null;
        try {
            packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
        } catch (RemoteException e) {
            /* ignore - local call */
        public void destroy() {
            token.unlinkToDeath(this, 0);
        }
        if (packageNames == null) {
            return EmptyArray.STRING;

        private boolean isDefault(boolean[] array) {
            if (ArrayUtils.isEmpty(array)) {
                return true;
            }
            for (boolean value : array) {
                if (value) {
                    return false;
                }
            }
            return true;
        }
        return packageNames;
    }
}