blob: 2d498ea2117cb8324b5310c22e2f3732f957a8a5 [file] [log] [blame]
/*
* 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_NONE;
import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager.opRestrictsRead;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.Mode;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import libcore.util.EmptyArray;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Objects;
/**
* Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
* In the future this class will also include mode callbacks and op restrictions.
*/
public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
// Must be the same object that the AppOpsService is using for locking.
final Object mLock;
final Handler mHandler;
final Context mContext;
final SparseArray<int[]> mSwitchedOps;
@GuardedBy("mLock")
@VisibleForTesting
final SparseArray<SparseIntArray> mUidModes = new SparseArray<>();
@GuardedBy("mLock")
final ArrayMap<String, SparseIntArray> mPackageModes = new ArrayMap<>();
final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>();
final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
new ArrayMap<>();
final PersistenceScheduler mPersistenceScheduler;
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
@NonNull Object lock, Handler handler, Context context,
SparseArray<int[]> switchedOps) {
this.mPersistenceScheduler = persistenceScheduler;
this.mLock = lock;
this.mHandler = handler;
this.mContext = context;
this.mSwitchedOps = switchedOps;
}
@Override
public SparseIntArray getNonDefaultUidModes(int uid) {
synchronized (mLock) {
SparseIntArray opModes = mUidModes.get(uid, null);
if (opModes == null) {
return new SparseIntArray();
}
return opModes.clone();
}
}
@Override
public int getUidMode(int uid, int op) {
synchronized (mLock) {
SparseIntArray opModes = mUidModes.get(uid, null);
if (opModes == null) {
return AppOpsManager.opToDefaultMode(op);
}
return opModes.get(op, AppOpsManager.opToDefaultMode(op));
}
}
@Override
public boolean setUidMode(int uid, int op, int mode) {
final int defaultMode = AppOpsManager.opToDefaultMode(op);
synchronized (mLock) {
SparseIntArray opModes = mUidModes.get(uid, null);
if (opModes == null) {
if (mode != defaultMode) {
opModes = new SparseIntArray();
mUidModes.put(uid, opModes);
opModes.put(op, mode);
mPersistenceScheduler.scheduleWriteLocked();
}
} else {
if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
return false;
}
if (mode == defaultMode) {
opModes.delete(op);
if (opModes.size() <= 0) {
opModes = null;
mUidModes.delete(uid);
}
} else {
opModes.put(op, mode);
}
mPersistenceScheduler.scheduleWriteLocked();
}
}
return true;
}
@Override
public int getPackageMode(String packageName, int op) {
synchronized (mLock) {
SparseIntArray opModes = mPackageModes.getOrDefault(packageName, null);
if (opModes == null) {
return AppOpsManager.opToDefaultMode(op);
}
return opModes.get(op, AppOpsManager.opToDefaultMode(op));
}
}
@Override
public void setPackageMode(String packageName, int op, @Mode int mode) {
final int defaultMode = AppOpsManager.opToDefaultMode(op);
synchronized (mLock) {
SparseIntArray opModes = mPackageModes.get(packageName);
if (opModes == null) {
if (mode != defaultMode) {
opModes = new SparseIntArray();
mPackageModes.put(packageName, opModes);
opModes.put(op, mode);
mPersistenceScheduler.scheduleWriteLocked();
}
} else {
if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
return;
}
if (mode == defaultMode) {
opModes.delete(op);
if (opModes.size() <= 0) {
opModes = null;
mPackageModes.remove(packageName);
}
} else {
opModes.put(op, mode);
}
mPersistenceScheduler.scheduleWriteLocked();
}
}
}
@Override
public void removeUid(int uid) {
synchronized (mLock) {
SparseIntArray opModes = mUidModes.get(uid);
if (opModes == null) {
return;
}
mUidModes.remove(uid);
mPersistenceScheduler.scheduleFastWriteLocked();
}
}
@Override
public boolean areUidModesDefault(int uid) {
synchronized (mLock) {
SparseIntArray opModes = mUidModes.get(uid);
return (opModes == null || opModes.size() <= 0);
}
}
@Override
public boolean arePackageModesDefault(String packageMode) {
synchronized (mLock) {
SparseIntArray opModes = mPackageModes.get(packageMode);
return (opModes == null || opModes.size() <= 0);
}
}
@Override
public boolean removePackage(String packageName) {
synchronized (mLock) {
SparseIntArray ops = mPackageModes.remove(packageName);
if (ops != null) {
mPersistenceScheduler.scheduleFastWriteLocked();
return true;
}
return false;
}
}
@Override
public void clearAllModes() {
synchronized (mLock) {
mUidModes.clear();
mPackageModes.clear();
}
}
@Override
public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
int op) {
Objects.requireNonNull(changedListener);
synchronized (mLock) {
ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op);
if (modeWatcherSet == null) {
modeWatcherSet = new ArraySet<>();
mOpModeWatchers.put(op, modeWatcherSet);
}
modeWatcherSet.add(changedListener);
}
}
@Override
public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
@NonNull String packageName) {
Objects.requireNonNull(changedListener);
Objects.requireNonNull(packageName);
synchronized (mLock) {
ArraySet<OnOpModeChangedListener> modeWatcherSet =
mPackageModeWatchers.get(packageName);
if (modeWatcherSet == null) {
modeWatcherSet = new ArraySet<>();
mPackageModeWatchers.put(packageName, modeWatcherSet);
}
modeWatcherSet.add(changedListener);
}
}
@Override
public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
Objects.requireNonNull(changedListener);
synchronized (mLock) {
for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
cbs.remove(changedListener);
if (cbs.size() <= 0) {
mOpModeWatchers.removeAt(i);
}
}
for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
cbs.remove(changedListener);
if (cbs.size() <= 0) {
mPackageModeWatchers.removeAt(i);
}
}
}
}
@Override
public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
synchronized (mLock) {
ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op);
if (modeChangedListenersSet == null) {
return new ArraySet<>();
}
return new ArraySet<>(modeChangedListenersSet);
}
}
@Override
public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
@NonNull String packageName) {
Objects.requireNonNull(packageName);
synchronized (mLock) {
ArraySet<OnOpModeChangedListener> modeChangedListenersSet =
mPackageModeWatchers.get(packageName);
if (modeChangedListenersSet == null) {
return new ArraySet<>();
}
return new ArraySet<>(modeChangedListenersSet);
}
}
@Override
public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
int uid, @Nullable String packageName) {
Objects.requireNonNull(onModeChangedListener);
if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
&& onModeChangedListener.getWatchingUid() != uid) {
return;
}
// See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
int[] switchedCodes;
if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
switchedCodes = mSwitchedOps.get(code);
} else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
switchedCodes = new int[]{code};
} else {
switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
}
for (int switchedCode : switchedCodes) {
// There are features watching for mode changes such as window manager
// and location manager which are in our process. The callbacks in these
// features may require permissions our remote caller does not have.
final long identity = Binder.clearCallingIdentity();
try {
if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
onModeChangedListener.getCallingUid())) {
continue;
}
onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
} catch (RemoteException e) {
/* ignore */
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
// If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
// as watcher should not use this to signal if the value is changed.
return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
}
@Override
public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
@Nullable OnOpModeChangedListener callbackToIgnore) {
String[] uidPackageNames = getPackagesForUid(uid);
ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
synchronized (mLock) {
ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
if (callbacks != null) {
final int callbackCount = callbacks.size();
for (int i = 0; i < callbackCount; i++) {
OnOpModeChangedListener callback = callbacks.valueAt(i);
if (onlyForeground && (callback.getFlags()
& WATCH_FOREGROUND_CHANGES) == 0) {
continue;
}
ArraySet<String> changedPackages = new ArraySet<>();
Collections.addAll(changedPackages, uidPackageNames);
if (callbackSpecs == null) {
callbackSpecs = new ArrayMap<>();
}
callbackSpecs.put(callback, changedPackages);
}
}
for (String uidPackageName : uidPackageNames) {
callbacks = mPackageModeWatchers.get(uidPackageName);
if (callbacks != null) {
if (callbackSpecs == null) {
callbackSpecs = new ArrayMap<>();
}
final int callbackCount = callbacks.size();
for (int i = 0; i < callbackCount; i++) {
OnOpModeChangedListener callback = callbacks.valueAt(i);
if (onlyForeground && (callback.getFlags()
& WATCH_FOREGROUND_CHANGES) == 0) {
continue;
}
ArraySet<String> changedPackages = callbackSpecs.get(callback);
if (changedPackages == null) {
changedPackages = new ArraySet<>();
callbackSpecs.put(callback, changedPackages);
}
changedPackages.add(uidPackageName);
}
}
}
if (callbackSpecs != null && callbackToIgnore != null) {
callbackSpecs.remove(callbackToIgnore);
}
}
if (callbackSpecs == null) {
return;
}
for (int i = 0; i < callbackSpecs.size(); i++) {
final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
if (reportedPackageNames == null) {
mHandler.sendMessage(PooledLambda.obtainMessage(
LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
this, callback, code, uid, (String) null));
} else {
final int reportedPackageCount = reportedPackageNames.size();
for (int j = 0; j < reportedPackageCount; j++) {
final String reportedPackageName = reportedPackageNames.valueAt(j);
mHandler.sendMessage(PooledLambda.obtainMessage(
LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
this, callback, code, uid, reportedPackageName));
}
}
}
}
private static String[] getPackagesForUid(int uid) {
String[] packageNames = null;
// Very early during boot the package manager is not yet or not yet fully started. At this
// time there are no packages yet.
if (AppGlobals.getPackageManager() != null) {
try {
packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
} catch (RemoteException e) {
/* ignore - local call */
}
}
if (packageNames == null) {
return EmptyArray.STRING;
}
return packageNames;
}
@Override
public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
synchronized (mLock) {
return evalForegroundOps(mUidModes.get(uid), foregroundOps);
}
}
@Override
public SparseBooleanArray evalForegroundPackageOps(String packageName,
SparseBooleanArray foregroundOps) {
synchronized (mLock) {
return evalForegroundOps(mPackageModes.get(packageName), foregroundOps);
}
}
private SparseBooleanArray evalForegroundOps(SparseIntArray opModes,
SparseBooleanArray foregroundOps) {
SparseBooleanArray tempForegroundOps = foregroundOps;
if (opModes != null) {
for (int i = opModes.size() - 1; i >= 0; i--) {
if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
if (tempForegroundOps == null) {
tempForegroundOps = new SparseBooleanArray();
}
evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps);
}
}
}
return tempForegroundOps;
}
private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) {
boolean curValue = foregroundOps.get(op, false);
ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op);
if (listenerSet != null) {
for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) {
if ((listenerSet.valueAt(cbi).getFlags()
& AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
curValue = true;
}
}
}
foregroundOps.put(op, curValue);
}
@Override
public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
PrintWriter printWriter) {
boolean needSep = false;
if (mOpModeWatchers.size() > 0) {
boolean printedHeader = false;
for (int i = 0; i < mOpModeWatchers.size(); i++) {
if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
continue;
}
boolean printedOpHeader = false;
ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
mOpModeWatchers.valueAt(i);
for (int j = 0; j < modeChangedListenerSet.size(); j++) {
final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j);
if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(listener.getWatchingUid())) {
continue;
}
needSep = true;
if (!printedHeader) {
printWriter.println(" Op mode watchers:");
printedHeader = true;
}
if (!printedOpHeader) {
printWriter.print(" Op ");
printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
printWriter.println(":");
printedOpHeader = true;
}
printWriter.print(" #"); printWriter.print(j); printWriter.print(": ");
printWriter.println(listener.toString());
}
}
}
if (mPackageModeWatchers.size() > 0 && dumpOp < 0) {
boolean printedHeader = false;
for (int i = 0; i < mPackageModeWatchers.size(); i++) {
if (dumpPackage != null
&& !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
continue;
}
needSep = true;
if (!printedHeader) {
printWriter.println(" Package mode watchers:");
printedHeader = true;
}
printWriter.print(" Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i));
printWriter.println(":");
ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
mPackageModeWatchers.valueAt(i);
for (int j = 0; j < modeChangedListenerSet.size(); j++) {
printWriter.print(" #"); printWriter.print(j); printWriter.print(": ");
printWriter.println(modeChangedListenerSet.valueAt(j).toString());
}
}
}
return needSep;
}
}