blob: 1f3795a12b766c9fc39c678856ac67bcc7b393e6 [file] [log] [blame]
/*
* Copyright (C) 2012 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.ATTRIBUTION_CHAIN_ID_NONE;
import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
import static android.app.AppOpsManager.FILTER_BY_UID;
import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
import static android.app.AppOpsManager.OP_VIBRATE;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
import static android.app.AppOpsManager.OpEventProxyInfo;
import static android.app.AppOpsManager.RestrictionBypass;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
import static android.app.AppOpsManager._NUM_OP;
import static android.app.AppOpsManager.extractFlagsFromKey;
import static android.app.AppOpsManager.extractUidStateFromKey;
import static android.app.AppOpsManager.modeToName;
import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
import static android.app.AppOpsManager.opRestrictsRead;
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.AttributedOpEntry;
import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManager.Mode;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.AsyncNotedAppOp;
import android.app.RuntimeAppOpAccessMessage;
import android.app.SyncNotedAppOp;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.PackageTagsList;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
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.KeyValueListParser;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsStartedCallback;
import com.android.internal.app.MessageSamplingConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.os.Clock;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
import com.android.server.pm.PackageList;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.component.ParsedAttribution;
import com.android.server.policy.AppOpsPolicy;
import dalvik.annotation.optimization.NeverCompile;
import libcore.util.EmptyArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
public class AppOpsService extends IAppOpsService.Stub {
static final String TAG = "AppOps";
static final boolean DEBUG = false;
/**
* Used for data access validation collection, we wish to only log a specific access once
*/
private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
/**
* Version of the mRecentAccessesFile.
* Increment by one every time an upgrade step is added at boot, none currently exists.
*/
private static final int CURRENT_VERSION = 1;
// Write at most every 30 minutes.
static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
OP_PLAY_AUDIO,
OP_RECORD_AUDIO,
OP_CAMERA,
OP_VIBRATE,
};
private static final int MAX_UNFORWARDED_OPS = 10;
private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
final Context mContext;
final AtomicFile mStorageFile;
final AtomicFile mRecentAccessesFile;
private final @Nullable File mNoteOpCallerStacktracesFile;
final Handler mHandler;
/**
* Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
* objects
*/
@GuardedBy("this")
final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
/**
* Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
* new objects
*/
@GuardedBy("this")
final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
MAX_UNUSED_POOLED_OBJECTS);
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
= new AppOpsManagerInternalImpl();
@Nullable private final DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/**
* Registered callbacks, called from {@link #collectAsyncNotedOp}.
*
* <p>(package name, uid) -> callbacks
*
* @see #getAsyncNotedOpsKey(String, int)
*/
@GuardedBy("this")
private final ArrayMap<Pair<String, Integer>, RemoteCallbackList<IAppOpsAsyncNotedCallback>>
mAsyncOpWatchers = new ArrayMap<>();
/**
* Async note-ops collected from {@link #collectAsyncNotedOp} that have not been delivered to a
* callback yet.
*
* <p>(package name, uid) -> list&lt;ops&gt;
*
* @see #getAsyncNotedOpsKey(String, int)
*/
@GuardedBy("this")
private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
mUnforwardedAsyncNotedOps = new ArrayMap<>();
boolean mWriteNoteOpsScheduled;
boolean mWriteScheduled;
boolean mFastWriteScheduled;
final Runnable mWriteRunner = new Runnable() {
public void run() {
synchronized (AppOpsService.this) {
mWriteScheduled = false;
mFastWriteScheduled = false;
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
writeRecentAccesses();
return null;
}
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
}
}
};
@GuardedBy("this")
@VisibleForTesting
final SparseArray<UidState> mUidStates = new SparseArray<>();
volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
/*
* These are app op restrictions imposed per user from various parties.
*/
private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
new ArrayMap<>();
/*
* These are app op restrictions imposed globally from various parties within the system.
*/
private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
new ArrayMap<>();
SparseIntArray mProfileOwners;
private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
/**
* Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
* changed
*/
private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
private ActivityManagerInternal mActivityManagerInternal;
/** Package sampled for message collection in the current session */
@GuardedBy("this")
private String mSampledPackage = null;
/** Appop sampled for message collection in the current session */
@GuardedBy("this")
private int mSampledAppOpCode = OP_NONE;
/** Maximum distance for appop to be considered for message collection in the current session */
@GuardedBy("this")
private int mAcceptableLeftDistance = 0;
/** Number of messages collected for sampled package and appop in the current session */
@GuardedBy("this")
private float mMessagesCollectedCount;
/** List of rarely used packages priorities for message collection */
@GuardedBy("this")
private ArraySet<String> mRarelyUsedPackages = new ArraySet<>();
/** Sampling strategy used for current session */
@GuardedBy("this")
@AppOpsManager.SamplingStrategy
private int mSamplingStrategy;
/** Last runtime permission access message collected and ready for reporting */
@GuardedBy("this")
private RuntimeAppOpAccessMessage mCollectedRuntimePermissionMessage;
/** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
private @Nullable PackageManagerInternal mPackageManagerInternal;
/** Package Manager local. Access via {@link #getPackageManagerLocal()} */
private @Nullable PackageManagerLocal mPackageManagerLocal;
/** User Manager internal. Access via {@link #getUserManagerInternal()} */
private @Nullable UserManagerInternal mUserManagerInternal;
/** Interface for app-op modes.*/
@VisibleForTesting
AppOpsCheckingServiceInterface mAppOpsCheckingService;
/** Interface for app-op restrictions.*/
@VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
private AppOpsUidStateTracker mUidStateTracker;
/** Hands the definition of foreground and uid states */
@GuardedBy("this")
public AppOpsUidStateTracker getUidStateTracker() {
if (mUidStateTracker == null) {
mUidStateTracker = new AppOpsUidStateTrackerImpl(
LocalServices.getService(ActivityManagerInternal.class),
mHandler,
r -> {
synchronized (AppOpsService.this) {
r.run();
}
},
Clock.SYSTEM_CLOCK, mConstants);
mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
this::onUidStateChanged);
}
return mUidStateTracker;
}
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
* holding the AppOpsService lock.
*/
final class Constants extends ContentObserver {
/**
* How long we want for a drop in uid state from top to settle before applying it.
* @see Settings.Global#APP_OPS_CONSTANTS
* @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
*/
public long TOP_STATE_SETTLE_TIME;
/**
* How long we want for a drop in uid state from foreground to settle before applying it.
* @see Settings.Global#APP_OPS_CONSTANTS
* @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
*/
public long FG_SERVICE_STATE_SETTLE_TIME;
/**
* How long we want for a drop in uid state from background to settle before applying it.
* @see Settings.Global#APP_OPS_CONSTANTS
* @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
*/
public long BG_STATE_SETTLE_TIME;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private ContentResolver mResolver;
public Constants(Handler handler) {
super(handler);
updateConstants();
}
public void startMonitoring(ContentResolver resolver) {
mResolver = resolver;
mResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
false, this);
updateConstants();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
updateConstants();
}
private void updateConstants() {
String value = mResolver != null ? Settings.Global.getString(mResolver,
Settings.Global.APP_OPS_CONSTANTS) : "";
synchronized (AppOpsService.this) {
try {
mParser.setString(value);
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
Slog.e(TAG, "Bad app ops settings", e);
}
TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
}
}
void dump(PrintWriter pw) {
pw.println(" Settings:");
pw.print(" "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
pw.println();
pw.print(" "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
pw.println();
pw.print(" "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
pw.println();
}
}
@VisibleForTesting
final Constants mConstants;
@VisibleForTesting
final class UidState {
public final int uid;
@NonNull
public final ArrayMap<String, Ops> pkgOps = new ArrayMap<>();
// true indicates there is an interested observer, false there isn't but it has such an op
//TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
public SparseBooleanArray foregroundOps;
public boolean hasForegroundWatchers;
public UidState(int uid) {
this.uid = uid;
}
public void clear() {
mAppOpsCheckingService.removeUid(uid);
for (int i = 0; i < pkgOps.size(); i++) {
String packageName = pkgOps.keyAt(i);
mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
}
}
// Functions for uid mode access and manipulation.
public SparseIntArray getNonDefaultUidModes() {
return mAppOpsCheckingService.getNonDefaultUidModes(uid);
}
public int getUidMode(int op) {
return mAppOpsCheckingService.getUidMode(uid, op);
}
public boolean setUidMode(int op, int mode) {
return mAppOpsCheckingService.setUidMode(uid, op, mode);
}
@SuppressWarnings("GuardedBy")
int evalMode(int op, int mode) {
return getUidStateTracker().evalMode(uid, op, mode);
}
public void evalForegroundOps() {
foregroundOps = null;
foregroundOps = mAppOpsCheckingService.evalForegroundUidOps(uid, foregroundOps);
for (int i = pkgOps.size() - 1; i >= 0; i--) {
foregroundOps = mAppOpsCheckingService
.evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
UserHandle.getUserId(uid));
}
hasForegroundWatchers = false;
if (foregroundOps != null) {
for (int i = 0; i < foregroundOps.size(); i++) {
if (foregroundOps.valueAt(i)) {
hasForegroundWatchers = true;
break;
}
}
}
}
@SuppressWarnings("GuardedBy")
public int getState() {
return getUidStateTracker().getUidState(uid);
}
@SuppressWarnings("GuardedBy")
public void dump(PrintWriter pw, long nowElapsed) {
getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
}
}
final static class Ops extends SparseArray<Op> {
final String packageName;
final UidState uidState;
/**
* The restriction properties of the package. If {@code null} it could not have been read
* yet and has to be refreshed.
*/
@Nullable RestrictionBypass bypass;
/** Lazily populated cache of attributionTags of this package */
final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
/**
* Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
* than or equal to {@link #knownAttributionTags}.
*/
final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
Ops(String _packageName, UidState _uidState) {
packageName = _packageName;
uidState = _uidState;
}
}
/** Returned from {@link #verifyAndGetBypass(int, String, String, String, boolean)}. */
private static final class PackageVerificationResult {
final RestrictionBypass bypass;
final boolean isAttributionTagValid;
PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
this.bypass = bypass;
this.isAttributionTagValid = isAttributionTagValid;
}
}
final class Op {
int op;
int uid;
final UidState uidState;
final @NonNull String packageName;
/** attributionTag -> AttributedOp */
final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
Op(UidState uidState, String packageName, int op, int uid) {
this.op = op;
this.uid = uid;
this.uidState = uidState;
this.packageName = packageName;
}
@Mode int getMode() {
return mAppOpsCheckingService.getPackageMode(packageName, this.op,
UserHandle.getUserId(this.uid));
}
void setMode(@Mode int mode) {
mAppOpsCheckingService.setPackageMode(packageName, this.op, mode,
UserHandle.getUserId(this.uid));
}
void removeAttributionsWithNoTime() {
for (int i = mAttributions.size() - 1; i >= 0; i--) {
if (!mAttributions.valueAt(i).hasAnyTime()) {
mAttributions.removeAt(i);
}
}
}
private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
@Nullable String attributionTag) {
AttributedOp attributedOp;
attributedOp = mAttributions.get(attributionTag);
if (attributedOp == null) {
attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
mAttributions.put(attributionTag, attributedOp);
}
return attributedOp;
}
@NonNull OpEntry createEntryLocked() {
final int numAttributions = mAttributions.size();
final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
new ArrayMap<>(numAttributions);
for (int i = 0; i < numAttributions; i++) {
attributionEntries.put(mAttributions.keyAt(i),
mAttributions.valueAt(i).createAttributedOpEntryLocked());
}
return new OpEntry(op, getMode(), attributionEntries);
}
@NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
final int numAttributions = mAttributions.size();
final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
for (int i = 0; i < numAttributions; i++) {
if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
attributionEntries.put(mAttributions.keyAt(i),
mAttributions.valueAt(i).createAttributedOpEntryLocked());
break;
}
}
return new OpEntry(op, getMode(), attributionEntries);
}
boolean isRunning() {
final int numAttributions = mAttributions.size();
for (int i = 0; i < numAttributions; i++) {
if (mAttributions.valueAt(i).isRunning()) {
return true;
}
}
return false;
}
}
final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
/** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
public static final int ALL_OPS = -2;
// Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
// Otherwise we can just use the IBinder object.
private final IAppOpsCallback mCallback;
ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
int callingUid, int callingPid) {
super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
this.mCallback = callback;
try {
mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
/*ignored*/
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("ModeCallback{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" watchinguid=");
UserHandle.formatUid(sb, getWatchingUid());
sb.append(" flags=0x");
sb.append(Integer.toHexString(getFlags()));
switch (getWatchedOpCode()) {
case OP_NONE:
break;
case ALL_OPS:
sb.append(" op=(all)");
break;
default:
sb.append(" op=");
sb.append(opToName(getWatchedOpCode()));
break;
}
sb.append(" from uid=");
UserHandle.formatUid(sb, getCallingUid());
sb.append(" pid=");
sb.append(getCallingPid());
sb.append('}');
return sb.toString();
}
void unlinkToDeath() {
mCallback.asBinder().unlinkToDeath(this, 0);
}
@Override
public void binderDied() {
stopWatchingMode(mCallback);
}
@Override
public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
mCallback.opChanged(op, uid, packageName);
}
}
final class ActiveCallback implements DeathRecipient {
final IAppOpsActiveCallback mCallback;
final int mWatchingUid;
final int mCallingUid;
final int mCallingPid;
ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
int callingPid) {
mCallback = callback;
mWatchingUid = watchingUid;
mCallingUid = callingUid;
mCallingPid = callingPid;
try {
mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
/*ignored*/
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("ActiveCallback{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" watchinguid=");
UserHandle.formatUid(sb, mWatchingUid);
sb.append(" from uid=");
UserHandle.formatUid(sb, mCallingUid);
sb.append(" pid=");
sb.append(mCallingPid);
sb.append('}');
return sb.toString();
}
void destroy() {
mCallback.asBinder().unlinkToDeath(this, 0);
}
@Override
public void binderDied() {
stopWatchingActive(mCallback);
}
}
final class StartedCallback implements DeathRecipient {
final IAppOpsStartedCallback mCallback;
final int mWatchingUid;
final int mCallingUid;
final int mCallingPid;
StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
int callingPid) {
mCallback = callback;
mWatchingUid = watchingUid;
mCallingUid = callingUid;
mCallingPid = callingPid;
try {
mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
/*ignored*/
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("StartedCallback{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" watchinguid=");
UserHandle.formatUid(sb, mWatchingUid);
sb.append(" from uid=");
UserHandle.formatUid(sb, mCallingUid);
sb.append(" pid=");
sb.append(mCallingPid);
sb.append('}');
return sb.toString();
}
void destroy() {
mCallback.asBinder().unlinkToDeath(this, 0);
}
@Override
public void binderDied() {
stopWatchingStarted(mCallback);
}
}
final class NotedCallback implements DeathRecipient {
final IAppOpsNotedCallback mCallback;
final int mWatchingUid;
final int mCallingUid;
final int mCallingPid;
NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
int callingPid) {
mCallback = callback;
mWatchingUid = watchingUid;
mCallingUid = callingUid;
mCallingPid = callingPid;
try {
mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
/*ignored*/
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("NotedCallback{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" watchinguid=");
UserHandle.formatUid(sb, mWatchingUid);
sb.append(" from uid=");
UserHandle.formatUid(sb, mCallingUid);
sb.append(" pid=");
sb.append(mCallingPid);
sb.append('}');
return sb.toString();
}
void destroy() {
mCallback.asBinder().unlinkToDeath(this, 0);
}
@Override
public void binderDied() {
stopWatchingNoted(mCallback);
}
}
/**
* Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
*/
static void onClientDeath(@NonNull AttributedOp attributedOp,
@NonNull IBinder clientId) {
attributedOp.onClientDeath(clientId);
}
/**
* Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
* so that we do not log the same operation twice between instances
*/
private void readNoteOpCallerStackTraces() {
try {
if (!mNoteOpCallerStacktracesFile.exists()) {
mNoteOpCallerStacktracesFile.createNewFile();
return;
}
try (Scanner read = new Scanner(mNoteOpCallerStacktracesFile)) {
read.useDelimiter("\\},");
while (read.hasNext()) {
String jsonOps = read.next();
mNoteOpCallerStacktraces.add(NoteOpTrace.fromJson(jsonOps));
}
}
} catch (Exception e) {
Slog.e(TAG, "Cannot parse traces noteOps", e);
}
}
@VisibleForTesting
public AppOpsService(File recentAccessesFile, File storageFile, Handler handler,
Context context) {
mContext = context;
for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
int switchCode = AppOpsManager.opToSwitch(switchedCode);
mSwitchedOps.put(switchCode,
ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
}
mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
new AppOpsCheckingServiceImpl(
storageFile, this, handler, context, mSwitchedOps));
//mAppOpsCheckingService = new AppOpsCheckingServiceLoggingDecorator(
// LocalServices.getService(AppOpsCheckingServiceInterface.class));
mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, mAppOpsCheckingService);
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mStorageFile = new AtomicFile(storageFile, "appops_legacy");
mRecentAccessesFile = new AtomicFile(recentAccessesFile, "appops_accesses");
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
"noteOpStackTraces.json");
readNoteOpCallerStackTraces();
} else {
mNoteOpCallerStacktracesFile = null;
}
mHandler = handler;
mConstants = new Constants(mHandler);
// To migrate storageFile to recentAccessesFile, these reads must be called in this order.
readRecentAccesses();
mAppOpsCheckingService.readState();
}
public void publish() {
ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
LocalManagerRegistry.addManager(AppOpsManagerLocal.class, new AppOpsManagerLocalImpl());
}
/** Handler for work when packages are updated */
private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String pkgName = intent.getData().getEncodedSchemeSpecificPart();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
if (pkg == null) {
return;
}
ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
ArraySet<String> attributionTags = new ArraySet<>();
attributionTags.add(null);
if (pkg.getAttributions() != null) {
int numAttributions = pkg.getAttributions().size();
for (int attributionNum = 0; attributionNum < numAttributions;
attributionNum++) {
ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
attributionTags.add(attribution.getTag());
int numInheritFrom = attribution.getInheritFrom().size();
for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
inheritFromNum++) {
dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
attribution.getTag());
}
}
}
synchronized (AppOpsService.this) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
}
Ops ops = uidState.pkgOps.get(pkgName);
if (ops == null) {
return;
}
// Reset cached package properties to re-initialize when needed
ops.bypass = null;
ops.knownAttributionTags.clear();
// Merge data collected for removed attributions into their successor
// attributions
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
int numAttributions = op.mAttributions.size();
for (int attributionNum = numAttributions - 1; attributionNum >= 0;
attributionNum--) {
String attributionTag = op.mAttributions.keyAt(attributionNum);
if (attributionTags.contains(attributionTag)) {
// attribution still exist after upgrade
continue;
}
String newAttributionTag = dstAttributionTags.get(attributionTag);
AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
newAttributionTag);
newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
op.mAttributions.removeAt(attributionNum);
scheduleFastWriteLocked();
}
}
}
}
}
};
public void systemReady() {
mAppOpsCheckingService.systemReady();
initializeUidStates();
mConstants.startMonitoring(mContext.getContentResolver());
mHistoricalRegistry.systemReady(mContext.getContentResolver());
IntentFilter packageUpdateFilter = new IntentFilter();
packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageUpdateFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
packageUpdateFilter, null, null);
synchronized (this) {
for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
int uid = mUidStates.keyAt(uidNum);
UidState uidState = mUidStates.valueAt(uidNum);
String[] pkgsInUid = getPackagesForUid(uidState.uid);
if (ArrayUtils.isEmpty(pkgsInUid)) {
uidState.clear();
mUidStates.removeAt(uidNum);
scheduleFastWriteLocked();
continue;
}
ArrayMap<String, Ops> pkgs = uidState.pkgOps;
int numPkgs = pkgs.size();
for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
String pkg = pkgs.keyAt(pkgNum);
String action;
if (!ArrayUtils.contains(pkgsInUid, pkg)) {
action = Intent.ACTION_PACKAGE_REMOVED;
} else {
action = Intent.ACTION_PACKAGE_REPLACED;
}
SystemServerInitThreadPool.submit(
() -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
.setData(Uri.fromParts("package", pkg, null))
.putExtra(Intent.EXTRA_UID, uid)),
"Update app-ops uidState in case package " + pkg + " changed");
}
}
}
getUserManagerInternal().addUserLifecycleListener(
new UserManagerInternal.UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, Object token) {
initializeUserUidStates(user.id);
}
// onUserRemoved handled by #removeUser
});
getPackageManagerInternal().getPackageList(
new PackageManagerInternal.PackageListObserver() {
@Override
public void onPackageAdded(String packageName, int appId) {
PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS, Process.myUid(),
mContext.getUserId());
boolean isSamplingTarget = isSamplingTarget(pi);
int[] userIds = getUserManagerInternal().getUserIds();
synchronized (AppOpsService.this) {
if (isSamplingTarget) {
mRarelyUsedPackages.add(packageName);
}
for (int i = 0; i < userIds.length; i++) {
int uid = UserHandle.getUid(userIds[i], appId);
UidState uidState = getUidStateLocked(uid, true);
if (!uidState.pkgOps.containsKey(packageName)) {
uidState.pkgOps.put(packageName,
new Ops(packageName, uidState));
}
}
}
}
@Override
public void onPackageRemoved(String packageName, int appId) {
int[] userIds = getUserManagerInternal().getUserIds();
synchronized (AppOpsService.this) {
for (int i = 0; i < userIds.length; i++) {
int uid = UserHandle.getUid(userIds[i], appId);
packageRemovedLocked(uid, packageName);
}
}
}
});
final IntentFilter packageSuspendFilter = new IntentFilter();
packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
final String[] changedPkgs = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_PACKAGE_LIST);
for (int code : OPS_RESTRICTED_ON_SUSPEND) {
ArraySet<OnOpModeChangedListener> onModeChangedListeners;
synchronized (AppOpsService.this) {
onModeChangedListeners =
mAppOpsCheckingService.getOpModeChangedListeners(code);
if (onModeChangedListeners == null) {
continue;
}
}
for (int i = 0; i < changedUids.length; i++) {
final int changedUid = changedUids[i];
final String changedPkg = changedPkgs[i];
// We trust packagemanager to insert matching uid and packageNames in the
// extras
notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
}
}
}
}, UserHandle.ALL, packageSuspendFilter, null, null);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
List<String> packageNames = getPackageListAndResample();
initializeRarelyUsedPackagesList(new ArraySet<>(packageNames));
}
}, RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS);
getPackageManagerInternal().setExternalSourcesPolicy(
new PackageManagerInternal.ExternalSourcesPolicy() {
@Override
public int getPackageTrustedToInstallApps(String packageName, int uid) {
int appOpMode = checkOperation(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
uid, packageName);
switch (appOpMode) {
case AppOpsManager.MODE_ALLOWED:
return PackageManagerInternal.ExternalSourcesPolicy.USER_TRUSTED;
case AppOpsManager.MODE_ERRORED:
return PackageManagerInternal.ExternalSourcesPolicy.USER_BLOCKED;
default:
return PackageManagerInternal.ExternalSourcesPolicy.USER_DEFAULT;
}
}
});
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
/**
* Initialize uid state objects for state contained in the checking service.
*/
@VisibleForTesting
void initializeUidStates() {
UserManagerInternal umi = getUserManagerInternal();
synchronized (this) {
int[] userIds = umi.getUserIds();
try (PackageManagerLocal.UnfilteredSnapshot snapshot =
getPackageManagerLocal().withUnfilteredSnapshot()) {
Map<String, PackageState> packageStates = snapshot.getPackageStates();
for (int i = 0; i < userIds.length; i++) {
int userId = userIds[i];
initializeUserUidStatesLocked(userId, packageStates);
}
}
}
}
private void initializeUserUidStates(int userId) {
synchronized (this) {
try (PackageManagerLocal.UnfilteredSnapshot snapshot =
getPackageManagerLocal().withUnfilteredSnapshot()) {
initializeUserUidStatesLocked(userId, snapshot.getPackageStates());
}
}
}
private void initializeUserUidStatesLocked(int userId, Map<String,
PackageState> packageStates) {
for (Map.Entry<String, PackageState> entry : packageStates.entrySet()) {
int appId = entry.getValue().getAppId();
String packageName = entry.getKey();
initializePackageUidStateLocked(userId, appId, packageName);
}
}
/*
Be careful not to clear any existing data; only want to add objects that don't already exist.
*/
private void initializePackageUidStateLocked(int userId, int appId, String packageName) {
int uid = UserHandle.getUid(userId, appId);
UidState uidState = getUidStateLocked(uid, true);
Ops ops = uidState.pkgOps.get(packageName);
if (ops == null) {
ops = new Ops(packageName, uidState);
uidState.pkgOps.put(packageName, ops);
}
SparseIntArray packageModes =
mAppOpsCheckingService.getNonDefaultPackageModes(packageName, userId);
for (int k = 0; k < packageModes.size(); k++) {
int code = packageModes.keyAt(k);
if (ops.indexOfKey(code) < 0) {
ops.put(code, new Op(uidState, packageName, code, uid));
}
}
uidState.evalForegroundOps();
}
/**
* Sets a policy for handling app ops.
*
* @param policy The policy.
*/
public void setAppOpsPolicy(@Nullable CheckOpsDelegate policy) {
final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
final CheckOpsDelegate delegate = (oldDispatcher != null)
? oldDispatcher.mCheckOpsDelegate : null;
mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
}
@VisibleForTesting
void packageRemoved(int uid, String packageName) {
synchronized (this) {
packageRemovedLocked(uid, packageName);
}
}
@GuardedBy("this")
private void packageRemovedLocked(int uid, String packageName) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
}
Ops removedOps = null;
// Remove any package state if such.
removedOps = uidState.pkgOps.remove(packageName);
mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
if (removedOps != null) {
scheduleFastWriteLocked();
final int numOps = removedOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = removedOps.valueAt(opNum);
final int numAttributions = op.mAttributions.size();
for (int attributionNum = 0; attributionNum < numAttributions;
attributionNum++) {
AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
while (attributedOp.isRunning()) {
attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
}
while (attributedOp.isPaused()) {
attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
}
}
}
}
mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
mHistoricalRegistry, uid, packageName));
}
public void uidRemoved(int uid) {
synchronized (this) {
if (mUidStates.indexOfKey(uid) >= 0) {
mUidStates.get(uid).clear();
mUidStates.remove(uid);
scheduleFastWriteLocked();
}
}
}
// The callback method from AppOpsUidStateTracker
private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
synchronized (this) {
UidState uidState = getUidStateLocked(uid, true);
if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
if (!uidState.foregroundOps.valueAt(fgi)) {
continue;
}
final int code = uidState.foregroundOps.keyAt(fgi);
if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
&& uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid,
this, code, uidState.uid, true, null));
} else if (!uidState.pkgOps.isEmpty()) {
final ArraySet<OnOpModeChangedListener> listenerSet =
mAppOpsCheckingService.getOpModeChangedListeners(code);
if (listenerSet != null) {
for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
if ((listener.getFlags()
& AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
|| !listener.isWatchingUid(uidState.uid)) {
continue;
}
for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
if (op == null) {
continue;
}
if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChanged,
this, listenerSet.valueAt(cbi), code, uidState.uid,
uidState.pkgOps.keyAt(pkgi)));
}
}
}
}
}
}
}
if (uidState != null) {
int numPkgs = uidState.pkgOps.size();
for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
Ops ops = uidState.pkgOps.valueAt(pkgNum);
int numOps = ops.size();
for (int opNum = 0; opNum < numOps; opNum++) {
Op op = ops.valueAt(opNum);
int numAttributions = op.mAttributions.size();
for (int attributionNum = 0; attributionNum < numAttributions;
attributionNum++) {
AttributedOp attributedOp = op.mAttributions.valueAt(
attributionNum);
attributedOp.onUidStateChanged(state);
}
}
}
}
}
}
/**
* Notify the proc state or capability has changed for a certain UID.
*/
public void updateUidProcState(int uid, int procState,
@ActivityManager.ProcessCapability int capability) {
synchronized (this) {
getUidStateTracker().updateUidProcState(uid, procState, capability);
if (!mUidStates.contains(uid)) {
UidState uidState = new UidState(uid);
mUidStates.put(uid, uidState);
onUidStateChanged(uid,
AppOpsUidStateTracker.processStateToUidState(procState), false);
}
}
}
public void shutdown() {
Slog.w(TAG, "Writing app ops before shutdown...");
boolean doWrite = false;
synchronized (this) {
if (mWriteScheduled) {
mWriteScheduled = false;
mFastWriteScheduled = false;
mHandler.removeCallbacks(mWriteRunner);
doWrite = true;
}
}
if (doWrite) {
writeRecentAccesses();
}
mAppOpsCheckingService.shutdown();
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
writeNoteOps();
}
mHistoricalRegistry.shutdown();
}
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
ArrayList<AppOpsManager.OpEntry> resOps = null;
if (ops == null) {
resOps = new ArrayList<>();
for (int j=0; j<pkgOps.size(); j++) {
Op curOp = pkgOps.valueAt(j);
resOps.add(getOpEntryForResult(curOp));
}
} else {
for (int j=0; j<ops.length; j++) {
Op curOp = pkgOps.get(ops[j]);
if (curOp != null) {
if (resOps == null) {
resOps = new ArrayList<>();
}
resOps.add(getOpEntryForResult(curOp));
}
}
}
return resOps;
}
@Nullable
private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
@Nullable int[] ops) {
final SparseIntArray opModes = uidState.getNonDefaultUidModes();
if (opModes == null) {
return null;
}
int opModeCount = opModes.size();
if (opModeCount == 0) {
return null;
}
ArrayList<AppOpsManager.OpEntry> resOps = null;
if (ops == null) {
resOps = new ArrayList<>();
for (int i = 0; i < opModeCount; i++) {
int code = opModes.keyAt(i);
resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
}
} else {
for (int j=0; j<ops.length; j++) {
int code = ops[j];
if (opModes.indexOfKey(code) >= 0) {
if (resOps == null) {
resOps = new ArrayList<>();
}
resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
}
}
}
return resOps;
}
private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
return op.createEntryLocked();
}
@Override
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
final int callingUid = Binder.getCallingUid();
final boolean hasAllPackageAccess = mContext.checkPermission(
Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
ArrayList<AppOpsManager.PackageOps> res = null;
synchronized (this) {
final int uidStateCount = mUidStates.size();
for (int i = 0; i < uidStateCount; i++) {
UidState uidState = mUidStates.valueAt(i);
if (uidState.pkgOps.isEmpty()) {
continue;
}
ArrayMap<String, Ops> packages = uidState.pkgOps;
final int packageCount = packages.size();
for (int j = 0; j < packageCount; j++) {
Ops pkgOps = packages.valueAt(j);
ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
if (resOps != null) {
if (res == null) {
res = new ArrayList<>();
}
AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
pkgOps.packageName, pkgOps.uidState.uid, resOps);
// Caller can always see their packages and with a permission all.
if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
res.add(resPackage);
}
}
}
}
}
return res;
}
@Override
public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
int[] ops) {
enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return Collections.emptyList();
}
synchronized (this) {
Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
/* edit */ false);
if (pkgOps == null) {
return null;
}
ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
if (resOps == null) {
return null;
}
ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
pkgOps.packageName, pkgOps.uidState.uid, resOps);
res.add(resPackage);
return res;
}
}
private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
// We get to access everything
final int callingPid = Binder.getCallingPid();
if (callingPid == Process.myPid()) {
return;
}
// Apps can access their own data
final int callingUid = Binder.getCallingUid();
if (uid == callingUid && packageName != null
&& checkPackage(uid, packageName) == MODE_ALLOWED) {
return;
}
// Otherwise, you need a permission...
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, callingPid,
callingUid, null);
}
/**
* Verify that historical appop request arguments are valid.
*/
private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
long endTimeMillis, int flags) {
if ((filter & FILTER_BY_UID) != 0) {
Preconditions.checkArgument(uid != Process.INVALID_UID);
} else {
Preconditions.checkArgument(uid == Process.INVALID_UID);
}
if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
Objects.requireNonNull(packageName);
} else {
Preconditions.checkArgument(packageName == null);
}
if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
Preconditions.checkArgument(attributionTag == null);
}
if ((filter & FILTER_BY_OP_NAMES) != 0) {
Objects.requireNonNull(opNames);
} else {
Preconditions.checkArgument(opNames == null);
}
Preconditions.checkFlagsArgument(filter,
FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
| FILTER_BY_OP_NAMES);
Preconditions.checkArgumentNonnegative(beginTimeMillis);
Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
}
@Override
public void getHistoricalOps(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
PackageManager pm = mContext.getPackageManager();
ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
if (!isSelfRequest) {
boolean isCallerInstrumented =
ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
boolean isCallerPermissionController;
try {
isCallerPermissionController = pm.getPackageUidAsUser(
mContext.getPackageManager().getPermissionControllerPackageName(), 0,
UserHandle.getUserId(Binder.getCallingUid()))
== Binder.getCallingUid();
} catch (PackageManager.NameNotFoundException doesNotHappen) {
return;
}
boolean doesCallerHavePermission = mContext.checkPermission(
android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid())
== PackageManager.PERMISSION_GRANTED;
if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
&& !doesCallerHavePermission) {
mHandler.post(() -> callback.sendResult(new Bundle()));
return;
}
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
}
final String[] opNamesArray = (opNames != null)
? opNames.toArray(new String[opNames.size()]) : null;
Set<String> attributionChainExemptPackages = null;
if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
attributionChainExemptPackages =
PermissionManager.getIndicatorExemptedPackages(mContext);
}
final String[] chainExemptPkgArray = attributionChainExemptPackages != null
? attributionChainExemptPackages.toArray(
new String[attributionChainExemptPackages.size()]) : null;
// Must not hold the appops lock
mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
callback).recycleOnUse());
}
@Override
public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
final String[] opNamesArray = (opNames != null)
? opNames.toArray(new String[opNames.size()]) : null;
Set<String> attributionChainExemptPackages = null;
if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
attributionChainExemptPackages =
PermissionManager.getIndicatorExemptedPackages(mContext);
}
final String[] chainExemptPkgArray = attributionChainExemptPackages != null
? attributionChainExemptPackages.toArray(
new String[attributionChainExemptPackages.size()]) : null;
// Must not hold the appops lock
mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
callback).recycleOnUse());
}
@Override
public void reloadNonHistoricalState() {
mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
mAppOpsCheckingService.writeState();
mAppOpsCheckingService.readState();
}
@VisibleForTesting
void readState() {
mAppOpsCheckingService.readState();
}
@Override
public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
synchronized (this) {
UidState uidState = getUidStateLocked(uid, false);
if (uidState == null) {
return null;
}
ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
if (resOps == null) {
return null;
}
ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
null, uidState.uid, resOps);
res.add(resPackage);
return res;
}
}
private void pruneOpLocked(Op op, int uid, String packageName) {
op.removeAttributionsWithNoTime();
if (op.mAttributions.isEmpty()) {
Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
if (ops != null) {
ops.remove(op.op);
op.setMode(AppOpsManager.opToDefaultMode(op.op));
if (ops.size() <= 0) {
UidState uidState = ops.uidState;
ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
if (pkgOps != null) {
pkgOps.remove(ops.packageName);
mAppOpsCheckingService.removePackage(ops.packageName,
UserHandle.getUserId(uidState.uid));
}
}
}
}
}
private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
if (callingPid == Process.myPid()) {
return;
}
final int callingUser = UserHandle.getUserId(callingUid);
synchronized (this) {
if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
// Profile owners are allowed to change modes but only for apps
// within their user.
return;
}
}
}
mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
@Override
public void setUidMode(int code, int uid, int mode) {
setUidMode(code, uid, mode, null);
if (code == OP_RUN_ANY_IN_BACKGROUND) {
// TODO (b/280869337): Remove this once we have the required data.
Slog.wtfStack(TAG, "setUidMode called for RUN_ANY_IN_BACKGROUND by uid: "
+ UserHandle.formatUid(Binder.getCallingUid()));
}
}
private void setUidMode(int code, int uid, int mode,
@Nullable IAppOpsCallback permissionPolicyCallback) {
if (DEBUG) {
Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+ " by uid " + Binder.getCallingUid());
}
enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
verifyIncomingOp(code);
code = AppOpsManager.opToSwitch(code);
if (permissionPolicyCallback == null) {
updatePermissionRevokedCompat(uid, code, mode);
}
int previousMode;
synchronized (this) {
final int defaultMode = AppOpsManager.opToDefaultMode(code);
UidState uidState = getUidStateLocked(uid, false);
if (uidState == null) {
if (mode == defaultMode) {
return;
}
uidState = new UidState(uid);
mUidStates.put(uid, uidState);
}
if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
previousMode = uidState.getUidMode(code);
} else {
// doesn't look right but is legacy behavior.
previousMode = MODE_DEFAULT;
}
if (!uidState.setUidMode(code, mode)) {
return;
}
uidState.evalForegroundOps();
if (mode != MODE_ERRORED && mode != previousMode) {
updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
}
}
notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
notifyOpChangedSync(code, uid, null, mode, previousMode);
}
/**
* Notify that an op changed for all packages in an uid.
*
* @param code The op that changed
* @param uid The uid the op was changed for
* @param onlyForeground Only notify watchers that watch for foreground changes
*/
private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
@Nullable IAppOpsCallback callbackToIgnore) {
ModeCallback listenerToIgnore = callbackToIgnore != null
? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
mAppOpsCheckingService.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
listenerToIgnore);
}
private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
PackageManager packageManager = mContext.getPackageManager();
if (packageManager == null) {
// This can only happen during early boot. At this time the permission state and appop
// state are in sync
return;
}
String[] packageNames = packageManager.getPackagesForUid(uid);
if (ArrayUtils.isEmpty(packageNames)) {
return;
}
String packageName = packageNames[0];
int[] ops = mSwitchedOps.get(switchCode);
for (int code : ops) {
String permissionName = AppOpsManager.opToPermission(code);
if (permissionName == null) {
continue;
}
if (packageManager.checkPermission(permissionName, packageName)
!= PackageManager.PERMISSION_GRANTED) {
continue;
}
PermissionInfo permissionInfo;
try {
permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
continue;
}
if (!permissionInfo.isRuntime()) {
continue;
}
boolean supportsRuntimePermissions = getPackageManagerInternal()
.getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
UserHandle user = UserHandle.getUserHandleForUid(uid);
boolean isRevokedCompat;
if (permissionInfo.backgroundPermission != null) {
if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
== PackageManager.PERMISSION_GRANTED) {
boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ " permission state, this is discouraged and you should revoke the"
+ " runtime permission instead: uid=" + uid + ", switchCode="
+ switchCode + ", mode=" + mode + ", permission="
+ permissionInfo.backgroundPermission);
}
final long identity = Binder.clearCallingIdentity();
try {
packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
isBackgroundRevokedCompat
? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
&& mode != AppOpsManager.MODE_FOREGROUND;
} else {
isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
}
if (isRevokedCompat && supportsRuntimePermissions) {
Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ " permission state, this is discouraged and you should revoke the"
+ " runtime permission instead: uid=" + uid + ", switchCode="
+ switchCode + ", mode=" + mode + ", permission=" + permissionName);
}
final long identity = Binder.clearCallingIdentity();
try {
packageManager.updatePermissionFlags(permissionName, packageName,
PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
int previousMode) {
final StorageManagerInternal storageManagerInternal =
LocalServices.getService(StorageManagerInternal.class);
if (storageManagerInternal != null) {
storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
}
}
/**
* Sets the mode for a certain op and uid.
*
* @param code The op code to set
* @param uid The UID for which to set
* @param packageName The package for which to set
* @param mode The new mode to set
*/
@Override
public void setMode(int code, int uid, @NonNull String packageName, int mode) {
setMode(code, uid, packageName, mode, null);
final int callingUid = Binder.getCallingUid();
if (code == OP_RUN_ANY_IN_BACKGROUND && mode != MODE_ALLOWED) {
// TODO (b/280869337): Remove this once we have the required data.
final String callingPackage = ArrayUtils.firstOrNull(getPackagesForUid(callingUid));
Slog.wtfStack(TAG,
"RUN_ANY_IN_BACKGROUND for package " + packageName + " changed to mode: "
+ modeToName(mode) + " via setMode. Calling package: " + callingPackage
+ ", calling uid: " + UserHandle.formatUid(callingUid)
+ ", calling pid: " + Binder.getCallingPid()
+ ", system pid: " + Process.myPid());
}
}
void setMode(int code, int uid, @NonNull String packageName, int mode,
@Nullable IAppOpsCallback permissionPolicyCallback) {
enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return;
}
ArraySet<OnOpModeChangedListener> repCbs = null;
code = AppOpsManager.opToSwitch(code);
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, null);
} catch (SecurityException e) {
if (Process.isIsolated(uid)) {
Slog.e(TAG, "Cannot setMode: isolated process");
} else {
Slog.e(TAG, "Cannot setMode", e);
}
return;
}
int previousMode = MODE_DEFAULT;
synchronized (this) {
UidState uidState = getUidStateLocked(uid, false);
Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
if (op != null) {
if (op.getMode() != mode) {
previousMode = op.getMode();
op.setMode(mode);
if (uidState != null) {
uidState.evalForegroundOps();
}
ArraySet<OnOpModeChangedListener> cbs =
mAppOpsCheckingService.getOpModeChangedListeners(code);
if (cbs != null) {
if (repCbs == null) {
repCbs = new ArraySet<>();
}
repCbs.addAll(cbs);
}
cbs = mAppOpsCheckingService.getPackageModeChangedListeners(packageName);
if (cbs != null) {
if (repCbs == null) {
repCbs = new ArraySet<>();
}
repCbs.addAll(cbs);
}
if (repCbs != null && permissionPolicyCallback != null) {
repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
}
if (mode == AppOpsManager.opToDefaultMode(op.op)) {
// If going into the default mode, prune this op
// if there is nothing else interesting in it.
pruneOpLocked(op, uid, packageName);
}
scheduleFastWriteLocked();
if (mode != MODE_ERRORED) {
updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
}
}
}
}
if (repCbs != null) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChanged,
this, repCbs, code, uid, packageName));
}
notifyOpChangedSync(code, uid, packageName, mode, previousMode);
}
private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
int uid, String packageName) {
for (int i = 0; i < callbacks.size(); i++) {
final OnOpModeChangedListener callback = callbacks.valueAt(i);
notifyOpChanged(callback, code, uid, packageName);
}
}
private void notifyOpChanged(OnOpModeChangedListener callback, int code,
int uid, String packageName) {
mAppOpsCheckingService.notifyOpChanged(callback, code, uid, packageName);
}
private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
int op, int uid, String packageName, int previousMode) {
boolean duplicate = false;
if (reports == null) {
reports = new ArrayList<>();
} else {
final int reportCount = reports.size();
for (int j = 0; j < reportCount; j++) {
ChangeRec report = reports.get(j);
if (report.op == op && report.pkg.equals(packageName)) {
duplicate = true;
break;
}
}
}
if (!duplicate) {
reports.add(new ChangeRec(op, uid, packageName, previousMode));
}
return reports;
}
private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
int op, int uid, String packageName, int previousMode,
ArraySet<OnOpModeChangedListener> cbs) {
if (cbs == null) {
return callbacks;
}
if (callbacks == null) {
callbacks = new HashMap<>();
}
final int N = cbs.size();
for (int i=0; i<N; i++) {
OnOpModeChangedListener cb = cbs.valueAt(i);
ArrayList<ChangeRec> reports = callbacks.get(cb);
ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
if (changed != reports) {
callbacks.put(cb, changed);
}
}
return callbacks;
}
static final class ChangeRec {
final int op;
final int uid;
final String pkg;
final int previous_mode;
ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
op = _op;
uid = _uid;
pkg = _pkg;
previous_mode = _previous_mode;
}
}
@Override
public void resetAllModes(int reqUserId, String reqPackageName) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
true, true, "resetAllModes", null);
int reqUid = -1;
if (reqPackageName != null) {
try {
reqUid = AppGlobals.getPackageManager().getPackageUid(
reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
} catch (RemoteException e) {
/* ignore - local call */
}
}
enforceManageAppOpsModes(callingPid, callingUid, reqUid);
HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
ArrayList<ChangeRec> allChanges = new ArrayList<>();
synchronized (this) {
boolean changed = false;
for (int i = mUidStates.size() - 1; i >= 0; i--) {
UidState uidState = mUidStates.valueAt(i);
SparseIntArray opModes = uidState.getNonDefaultUidModes();
if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
final int uidOpCount = opModes.size();
for (int j = uidOpCount - 1; j >= 0; j--) {
final int code = opModes.keyAt(j);
if (AppOpsManager.opAllowsReset(code)) {
int previousMode = opModes.valueAt(j);
int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED :
AppOpsManager.opToDefaultMode(code);
uidState.setUidMode(code, newMode);
for (String packageName : getPackagesForUid(uidState.uid)) {
callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
previousMode,
mAppOpsCheckingService.getOpModeChangedListeners(code));
callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
previousMode, mAppOpsCheckingService
.getPackageModeChangedListeners(packageName));
allChanges = addChange(allChanges, code, uidState.uid,
packageName, previousMode);
}
}
}
}
if (uidState.pkgOps.isEmpty()) {
continue;
}
if (reqUserId != UserHandle.USER_ALL
&& reqUserId != UserHandle.getUserId(uidState.uid)) {
// Skip any ops for a different user
continue;
}
Map<String, Ops> packages = uidState.pkgOps;
Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
boolean uidChanged = false;
while (it.hasNext()) {
Map.Entry<String, Ops> ent = it.next();
String packageName = ent.getKey();
if (reqPackageName != null && !reqPackageName.equals(packageName)) {
// Skip any ops for a different package
continue;
}
Ops pkgOps = ent.getValue();
for (int j=pkgOps.size()-1; j>=0; j--) {
Op curOp = pkgOps.valueAt(j);
if (shouldDeferResetOpToDpm(curOp.op)) {
deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
continue;
}
if (AppOpsManager.opAllowsReset(curOp.op)) {
int previousMode = curOp.getMode();
int newMode = isPackageOpGrantedByRole(packageName, uidState.uid,
curOp.op) ? MODE_ALLOWED : AppOpsManager.opToDefaultMode(
curOp.op);
if (previousMode == newMode) {
continue;
}
curOp.setMode(newMode);
changed = true;
uidChanged = true;
final int uid = curOp.uidState.uid;
callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
previousMode,
mAppOpsCheckingService.getOpModeChangedListeners(curOp.op));
callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
previousMode, mAppOpsCheckingService
.getPackageModeChangedListeners(packageName));
allChanges = addChange(allChanges, curOp.op, uid, packageName,
previousMode);
curOp.removeAttributionsWithNoTime();
if (curOp.mAttributions.isEmpty()) {
pkgOps.removeAt(j);
}
}
}
if (pkgOps.size() == 0) {
it.remove();
mAppOpsCheckingService.removePackage(packageName,
UserHandle.getUserId(uidState.uid));
}
}
if (uidChanged) {
uidState.evalForegroundOps();
}
}
if (changed) {
scheduleFastWriteLocked();
}
}
if (callbacks != null) {
for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
: callbacks.entrySet()) {
OnOpModeChangedListener cb = ent.getKey();
ArrayList<ChangeRec> reports = ent.getValue();
for (int i=0; i<reports.size(); i++) {
ChangeRec rep = reports.get(i);
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChanged,
this, cb, rep.op, rep.uid, rep.pkg));
}
}
}
int numChanges = allChanges.size();
for (int i = 0; i < numChanges; i++) {
ChangeRec change = allChanges.get(i);
notifyOpChangedSync(change.op, change.uid, change.pkg,
AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
}
}
private boolean isUidOpGrantedByRole(int uid, int code) {
if (!AppOpsManager.opIsUidAppOpPermission(code)) {
return false;
}
PackageManager packageManager = mContext.getPackageManager();
long token = Binder.clearCallingIdentity();
try {
// Permissions are managed by UIDs, but unfortunately a package name is required in API.
String packageName = ArrayUtils.firstOrNull(packageManager.getPackagesForUid(uid));
if (packageName == null) {
return false;
}
int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission(
code), packageName, UserHandle.getUserHandleForUid(uid));
return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0;
} finally {
Binder.restoreCallingIdentity(token);
}
}
private boolean isPackageOpGrantedByRole(@NonNull String packageName, int uid, int code) {
if (!AppOpsManager.opIsPackageAppOpPermission(code)) {
return false;
}
PackageManager packageManager = mContext.getPackageManager();
long token = Binder.clearCallingIdentity();
try {
int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission(
code), packageName, UserHandle.getUserHandleForUid(uid));
return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0;
} finally {
Binder.restoreCallingIdentity(token);
}
}
private boolean shouldDeferResetOpToDpm(int op) {
// TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
// pre-grants to a role-based mechanism or another general-purpose mechanism.
return dpmi != null && dpmi.supportsResetOp(op);
}
/** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
// TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
// pre-grants to a role-based mechanism or another general-purpose mechanism.
dpmi.resetOp(op, packageName, userId);
}
private void evalAllForegroundOpsLocked() {
for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
final UidState uidState = mUidStates.valueAt(uidi);
if (uidState.foregroundOps != null) {
uidState.evalForegroundOps();
}
}
}
@Override
public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
startWatchingModeWithFlags(op, packageName, 0, callback);
}
@Override
public void startWatchingModeWithFlags(int op, String packageName, int flags,
IAppOpsCallback callback) {
int watchedUid = -1;
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
// TODO: should have a privileged permission to protect this.
// Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
// the USAGE_STATS permission since this can provide information about when an
// app is in the foreground?
Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
if (callback == null) {
return;
}
final boolean mayWatchPackageName = packageName != null
&& !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
synchronized (this) {
int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
int notifiedOps;
if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
if (op == OP_NONE) {
notifiedOps = ALL_OPS;
} else {
notifiedOps = op;
}
} else {
notifiedOps = switchOp;
}
ModeCallback cb = mModeWatchers.get(callback.asBinder());
if (cb == null) {
cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
callingPid);
mModeWatchers.put(callback.asBinder(), cb);
}
if (switchOp != AppOpsManager.OP_NONE) {
mAppOpsCheckingService.startWatchingOpModeChanged(cb, switchOp);
}
if (mayWatchPackageName) {
mAppOpsCheckingService.startWatchingPackageModeChanged(cb, packageName);
}
evalAllForegroundOpsLocked();
}
}
@Override
public void stopWatchingMode(IAppOpsCallback callback) {
if (callback == null) {
return;
}
synchronized (this) {
ModeCallback cb = mModeWatchers.remove(callback.asBinder());
if (cb != null) {
cb.unlinkToDeath();
mAppOpsCheckingService.removeListener(cb);
}
evalAllForegroundOpsLocked();
}
}
public CheckOpsDelegate getAppOpsServiceDelegate() {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
return (dispatcher != null) ? dispatcher.getCheckOpsDelegate() : null;
}
}
public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
final CheckOpsDelegate policy = (oldDispatcher != null) ? oldDispatcher.mPolicy : null;
mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
}
}
@Override
public int checkOperationRaw(int code, int uid, String packageName,
@Nullable String attributionTag) {
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
true /*raw*/);
}
@Override
public int checkOperation(int code, int uid, String packageName) {
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
false /*raw*/);
}
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, boolean raw) {
verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return AppOpsManager.opToDefaultMode(code);
}
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
}
/**
* Get the mode of an app-op.
*
* @param code The code of the op
* @param uid The uid of the package the op belongs to
* @param packageName The package the op belongs to
* @param raw If the raw state of eval-ed state should be checked.
*
* @return The mode of the op
*/
private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, boolean raw) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, null);
} catch (SecurityException e) {
if (Process.isIsolated(uid)) {
Slog.e(TAG, "Cannot checkOperation: isolated process");
} else {
Slog.e(TAG, "Cannot checkOperation", e);
}
return AppOpsManager.opToDefaultMode(code);
}
if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
return AppOpsManager.MODE_IGNORED;
}
synchronized (this) {
if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
return AppOpsManager.MODE_IGNORED;
}
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
if (uidState != null
&& uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
final int rawMode = uidState.getUidMode(code);
return raw ? rawMode : uidState.evalMode(code, rawMode);
}
Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
if (op == null) {
return AppOpsManager.opToDefaultMode(code);
}
return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
}
}
@Override
public int checkAudioOperation(int code, int usage, int uid, String packageName) {
return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName);
}
private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) {
final int mode = mAudioRestrictionManager.checkAudioOperation(
code, usage, uid, packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
return mode;
}
return checkOperation(code, uid, packageName);
}
@Override
public void setAudioRestriction(int code, int usage, int uid, int mode,
String[] exceptionPackages) {
enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
verifyIncomingUid(uid);
verifyIncomingOp(code);
mAudioRestrictionManager.setZenModeAudioRestriction(
code, usage, uid, mode, exceptionPackages);
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
}
@Override
public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
mAudioRestrictionManager.setCameraAudioRestriction(mode);
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyWatchersOfChange, this,
AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyWatchersOfChange, this,
AppOpsManager.OP_VIBRATE, UID_ANY));
}
@Override
public int checkPackage(int uid, String packageName) {
Objects.requireNonNull(packageName);
try {
verifyAndGetBypass(uid, packageName, null, null, true);
// When the caller is the system, it's possible that the packageName is the special
// one (e.g., "root") which isn't actually existed.
if (resolveUid(packageName) == uid
|| (isPackageExisted(packageName)
&& !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
return AppOpsManager.MODE_ALLOWED;
}
return AppOpsManager.MODE_ERRORED;
} catch (SecurityException ignored) {
return AppOpsManager.MODE_ERRORED;
}
}
private boolean isPackageExisted(String packageName) {
return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
}
/**
* This method will check with PackageManager to determine if the package provided should
* be visible to the {@link Binder#getCallingUid()}.
*
* NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
*/
private boolean filterAppAccessUnlocked(String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
return LocalServices.getService(PackageManagerInternal.class)
.filterAppAccess(packageName, callingUid, userId);
}
@Override
public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation) {
return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
}
private SyncNotedAppOp noteProxyOperationImpl(int code, AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation) {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
if (!isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))
|| !isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
proxiedPackageName);
}
skipProxyOperation = skipProxyOperation
&& isCallerAndAttributionTrusted(attributionSource);
String resolveProxyPackageName = AppOpsManager.resolvePackageName(proxyUid,
proxyPackageName);
if (resolveProxyPackageName == null) {
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code,
proxiedAttributionTag, proxiedPackageName);
}
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
final boolean isProxyTrusted = mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
== PackageManager.PERMISSION_GRANTED || isSelfBlame;
if (!skipProxyOperation) {
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
proxiedPackageName);
}
}
String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
proxiedPackageName);
if (resolveProxiedPackageName == null) {
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
proxiedPackageName);
}
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
}
@Override
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
}
private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
shouldCollectAsyncNotedOp, message, shouldCollectMessage);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int proxyUid, String proxyPackageName,
@Nullable String proxyAttributionTag, @OpFlags int flags,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
boolean wasNull = attributionTag == null;
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
} catch (SecurityException e) {
if (Process.isIsolated(uid)) {
Slog.e(TAG, "Cannot noteOperation: isolated process");
} else {
Slog.e(TAG, "Cannot noteOperation", e);
}
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
AppOpsManager.MODE_IGNORED);
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
if (attributedOp.isRunning()) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ code + " startTime of in progress event="
+ attributedOp.mInProgressEvents.valueAt(0).getStartTime());
}
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
AppOpsManager.MODE_IGNORED);
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
uidMode);
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
: op;
final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
if (mode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
mode);
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
if (DEBUG) {
Slog.d(TAG,
"noteOperation: allowing code " + code + " uid " + uid + " package "
+ packageName + (attributionTag == null ? ""
: "." + attributionTag) + " flags: "
+ AppOpsManager.flagsToString(flags));
}
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
AppOpsManager.MODE_ALLOWED);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
uidState.getState(),
flags);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
shouldCollectMessage);
}
return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
packageName);
}
}
// TODO moltmann: Allow watching for attribution ops
@Override
public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
int watchedUid = Process.INVALID_UID;
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
!= PackageManager.PERMISSION_GRANTED) {
watchedUid = callingUid;
}
if (ops != null) {
Preconditions.checkArrayElementsInRange(ops, 0,
AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
}
if (callback == null) {
return;
}
synchronized (this) {
SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
if (callbacks == null) {
callbacks = new SparseArray<>();
mActiveWatchers.put(callback.asBinder(), callbacks);
}
final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
callingUid, callingPid);
for (int op : ops) {
callbacks.put(op, activeCallback);
}
}
}
@Override
public void stopWatchingActive(IAppOpsActiveCallback callback) {
if (callback == null) {
return;
}
synchronized (this) {
final SparseArray<ActiveCallback> activeCallbacks =
mActiveWatchers.remove(callback.asBinder());
if (activeCallbacks == null) {
return;
}
final int callbackCount = activeCallbacks.size();
for (int i = 0; i < callbackCount; i++) {
activeCallbacks.valueAt(i).destroy();
}
}
}
@Override
public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
int watchedUid = Process.INVALID_UID;
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
!= PackageManager.PERMISSION_GRANTED) {
watchedUid = callingUid;
}
Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
"Invalid op code in: " + Arrays.toString(ops));
Objects.requireNonNull(callback, "Callback cannot be null");
synchronized (this) {
SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
if (callbacks == null) {
callbacks = new SparseArray<>();
mStartedWatchers.put(callback.asBinder(), callbacks);
}
final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
callingUid, callingPid);
for (int op : ops) {
callbacks.put(op, startedCallback);
}
}
}
@Override
public void stopWatchingStarted(IAppOpsStartedCallback callback) {
Objects.requireNonNull(callback, "Callback cannot be null");
synchronized (this) {
final SparseArray<StartedCallback> startedCallbacks =
mStartedWatchers.remove(callback.asBinder());
if (startedCallbacks == null) {
return;
}
final int callbackCount = startedCallbacks.size();
for (int i = 0; i < callbackCount; i++) {
startedCallbacks.valueAt(i).destroy();
}
}
}
@Override
public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
int watchedUid = Process.INVALID_UID;
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
!= PackageManager.PERMISSION_GRANTED) {
watchedUid = callingUid;
}
Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
"Invalid op code in: " + Arrays.toString(ops));
Objects.requireNonNull(callback, "Callback cannot be null");
synchronized (this) {
SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
if (callbacks == null) {
callbacks = new SparseArray<>();
mNotedWatchers.put(callback.asBinder(), callbacks);
}
final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
callingUid, callingPid);
for (int op : ops) {
callbacks.put(op, notedCallback);
}
}
}
@Override
public void stopWatchingNoted(IAppOpsNotedCallback callback) {
Objects.requireNonNull(callback, "Callback cannot be null");
synchronized (this) {
final SparseArray<NotedCallback> notedCallbacks =
mNotedWatchers.remove(callback.asBinder());
if (notedCallbacks == null) {
return;
}
final int callbackCount = notedCallbacks.size();
for (int i = 0; i < callbackCount; i++) {
notedCallbacks.valueAt(i).destroy();
}
}
}
/**
* Collect an {@link AsyncNotedAppOp}.
*
* @param uid The uid the op was noted for
* @param packageName The package the op was noted for
* @param opCode The code of the op noted
* @param attributionTag attribution tag the op was noted for
* @param message The message for the op noting
*/
private void collectAsyncNotedOp(int uid, @NonNull String packageName, int opCode,
@Nullable String attributionTag, @OpFlags int flags, @NonNull String message,
boolean shouldCollectMessage) {
Objects.requireNonNull(message);
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
synchronized (this) {
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
attributionTag, message, System.currentTimeMillis());
final boolean[] wasNoteForwarded = {false};
if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) != 0
&& shouldCollectMessage) {
reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode,
attributionTag, message);
}
if (callbacks != null) {
callbacks.broadcast((cb) -> {
try {
cb.opNoted(asyncNotedOp);
wasNoteForwarded[0] = true;
} catch (RemoteException e) {
Slog.e(TAG,
"Could not forward noteOp of " + opCode + " to " + packageName
+ "/" + uid + "(" + attributionTag + ")", e);
}
});
}
if (!wasNoteForwarded[0]) {
ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
if (unforwardedOps == null) {
unforwardedOps = new ArrayList<>(1);
mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
}
unforwardedOps.add(asyncNotedOp);
if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
unforwardedOps.remove(0);
}
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Compute a key to be used in {@link #mAsyncOpWatchers} and {@link #mUnforwardedAsyncNotedOps}
*
* @param packageName The package name of the app
* @param uid The uid of the app
*
* @return They key uniquely identifying the app
*/
private @NonNull Pair<String, Integer> getAsyncNotedOpsKey(@NonNull String packageName,
int uid) {
return new Pair<>(packageName, uid);
}
@Override
public void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callback);
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
verifyAndGetBypass(uid, packageName, null);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
if (callbacks == null) {
callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
@Override
public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
synchronized (AppOpsService.this) {
if (getRegisteredCallbackCount() == 0) {
mAsyncOpWatchers.remove(key);
}
}
}
};
mAsyncOpWatchers.put(key, callbacks);
}
callbacks.register(callback);
}
}
@Override
public void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callback);
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
verifyAndGetBypass(uid, packageName, null);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
if (callbacks != null) {
callbacks.unregister(callback);
if (callbacks.getRegisteredCallbackCount() == 0) {
mAsyncOpWatchers.remove(key);
}
}
}
}
@Override
public List<AsyncNotedAppOp> extractAsyncOps(String packageName) {
Objects.requireNonNull(packageName);
int uid = Binder.getCallingUid();
verifyAndGetBypass(uid, packageName, null);
synchronized (this) {
return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
}
}
@Override
public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId) {
return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName,
attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, attributionFlags, attributionChainId);
}
private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
// As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
// OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
// also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
//
// TODO: Revert this change before Android 12.
int result = MODE_DEFAULT;
if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
|| code == OP_RECORD_AUDIO_SANDBOXED) {
result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
// Check result
if (result != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(result, code, attributionTag, packageName);
}
}
// As a special case for OP_CAMERA_SANDBOXED.
if (code == OP_CAMERA_SANDBOXED) {
result = checkOperation(OP_CAMERA, uid, packageName);
// Check result
if (result != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(result, code, attributionTag, packageName);
}
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
attributionChainId, /*dryRun*/ false);
}
@Override
public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
attributionChainId);
}
private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
int attributionChainId) {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, proxiedAttributionTag,
proxiedPackageName);
}
boolean isCallerTrusted = isCallerAndAttributionTrusted(attributionSource);
skipProxyOperation = isCallerTrusted && skipProxyOperation;
String resolvedProxyPackageName = AppOpsManager.resolvePackageName(proxyUid,
proxyPackageName);
if (resolvedProxyPackageName == null) {
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
proxiedPackageName);
}
final boolean isChainTrusted = isCallerTrusted
&& attributionChainId != ATTRIBUTION_CHAIN_ID_NONE
&& ((proxyAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) != 0
|| (proxiedAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) != 0);
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
final boolean isProxyTrusted = mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
== PackageManager.PERMISSION_GRANTED || isSelfBlame
|| isChainTrusted;
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
proxiedPackageName);
if (resolvedProxiedPackageName == null) {
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
proxiedPackageName);
}
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
return testProxiedOp;
}
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId,
/*dryRun*/ false);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
return proxyAppOp;
}
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
/*dryRun*/ false);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
}
private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
@NonNull String packageName, @Nullable String attributionTag, int proxyUid,
String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId, boolean dryRun) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
} catch (SecurityException e) {
if (Process.isIsolated(uid)) {
Slog.e(TAG, "Cannot startOperation: isolated process");
} else {
Slog.e(TAG, "Cannot startOperation", e);
}
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
boolean isRestricted = false;
int startType = START_TYPE_FAILED;
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
if (!dryRun) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
attributionChainId);
}
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
+ AppOpsManager.flagsToString(flags));
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
final UidState uidState = ops.uidState;
isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
false);
final int switchCode = AppOpsManager.opToSwitch(code);
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
}
if (!dryRun) {
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, uidMode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
: op;
final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
if (mode != AppOpsManager.MODE_ALLOWED
&& (!startIfModeDefault || mode != MODE_DEFAULT)) {
if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
if (!dryRun) {
attributedOp.rejected(uidState.getState(), flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, mode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ " package " + packageName + " restricted: " + isRestricted
+ " flags: " + AppOpsManager.flagsToString(flags));
if (!dryRun) {
try {
if (isRestricted) {
attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.getState(), flags,
attributionFlags, attributionChainId);
} else {
attributedOp.started(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.getState(), flags,
attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
attributionChainId);
}
}
if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
message, shouldCollectMessage);
}
return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
packageName);
}
@Override
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
mCheckOpsDelegateDispatcher.finishOperation(clientId, code, uid, packageName,
attributionTag);
}
private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return;
}
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return;
}
finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
}
@Override
public void finishProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation);
}
private Void finishProxyOperationImpl(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
skipProxyOperation = skipProxyOperation
&& isCallerAndAttributionTrusted(attributionSource);
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
return null;
}
String resolvedProxyPackageName = AppOpsManager.resolvePackageName(proxyUid,
proxyPackageName);
if (resolvedProxyPackageName == null) {
return null;
}
if (!skipProxyOperation) {
finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
proxyAttributionTag);
}
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
proxiedPackageName);
if (resolvedProxiedPackageName == null) {
return null;
}
finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag);
return null;
}
private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag);
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
} catch (SecurityException e) {
if (Process.isIsolated(uid)) {
Slog.e(TAG, "Cannot finishOperation: isolated process");
} else {
Slog.e(TAG, "Cannot finishOperation", e);
}
return;
}
synchronized (this) {
Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
pvr.bypass, /* edit */ true);
if (op == null) {
Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
return;
}
final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
if (attributedOp == null) {
Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
return;
}
if (attributedOp.isRunning() || attributedOp.isPaused()) {
attributedOp.finished(clientId);
} else {
Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
}
}
}
void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
int attributionFlags, int attributionChainId) {
ArraySet<ActiveCallback> dispatchedCallbacks = null;
final int callbackListCount = mActiveWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
ActiveCallback callback = callbacks.get(code);
if (callback != null) {
if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
continue;
}
if (dispatchedCallbacks == null) {
dispatchedCallbacks = new ArraySet<>();
}
dispatchedCallbacks.add(callback);
}
}
if (dispatchedCallbacks == null) {
return;
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpActiveChanged,
this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
attributionFlags, attributionChainId));
}
private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
// 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 {
final int callbackCount = callbacks.size();
for (int i = 0; i < callbackCount; i++) {
final ActiveCallback callback = callbacks.valueAt(i);
try {
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
active, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
String attributionTag, @OpFlags int flags, @Mode int result,
@AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<StartedCallback> dispatchedCallbacks = null;
final int callbackListCount = mStartedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
StartedCallback callback = callbacks.get(code);
if (callback != null) {
if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
continue;
}
if (dispatchedCallbacks == null) {
dispatchedCallbacks = new ArraySet<>();
}
dispatchedCallbacks.add(callback);
}
}
if (dispatchedCallbacks == null) {
return;
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted,
this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
result, startedType, attributionFlags, attributionChainId));
}
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
@Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
final long identity = Binder.clearCallingIdentity();
try {
final int callbackCount = callbacks.size();
for (int i = 0; i < callbackCount; i++) {
final StartedCallback callback = callbacks.valueAt(i);
try {
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
result, startedType, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
String attributionTag, @OpFlags int flags, @Mode int result) {
ArraySet<NotedCallback> dispatchedCallbacks = null;
final int callbackListCount = mNotedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
final NotedCallback callback = callbacks.get(code);
if (callback != null) {
if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
continue;
}
if (dispatchedCallbacks == null) {
dispatchedCallbacks = new ArraySet<>();
}
dispatchedCallbacks.add(callback);
}
}
if (dispatchedCallbacks == null) {
return;
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChecked,
this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
result));
}
private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
@Mode int result) {
// There are features watching for checks in our process. The callbacks in
// these features may require permissions our remote caller does not have.
final long identity = Binder.clearCallingIdentity();
try {
final int callbackCount = callbacks.size();
for (int i = 0; i < callbackCount; i++) {
final NotedCallback callback = callbacks.valueAt(i);
try {
if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
continue;
}
callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
result);
} catch (RemoteException e) {
/* do nothing */
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public int permissionToOpCode(String permission) {
if (permission == null) {
return AppOpsManager.OP_NONE;
}
return AppOpsManager.permissionToOpCode(permission);
}
@Override
public boolean shouldCollectNotes(int opCode) {
Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
if (AppOpsManager.shouldForceCollectNoteForOp(opCode)) {
return true;
}
String perm = AppOpsManager.opToPermission(opCode);
if (perm == null) {
return false;
}
PermissionInfo permInfo;
try {
permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
return permInfo.getProtection() == PROTECTION_DANGEROUS
|| (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0;
}
private void verifyIncomingProxyUid(@NonNull AttributionSource attributionSource) {
if (attributionSource.getUid() == Binder.getCallingUid()) {
return;
}
if (Binder.getCallingPid() == Process.myPid()) {
return;
}
if (attributionSource.isTrusted(mContext)) {
return;
}
mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
}
if (Binder.getCallingPid() == Process.myPid()) {
return;
}
mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
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;
}
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
// Enforce manage appops permission if it's a restricted read op.
if (opRestrictsRead(op)) {
mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
}
return;
}
throw new IllegalArgumentException("Bad operation #" + op);
}
private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
final int callingUid = Binder.getCallingUid();
// Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
if (packageName == null || isSpecialPackage(callingUid, packageName)) {
return true;
}
// If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
// the end. Although that exception would be caught and return, we could make it return
// early.
if (!isPackageExisted(packageName)) {
return false;
}
if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
Slog.w(TAG, packageName + " not found from " + callingUid);
return false;
}
return true;
}
private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
return callingUid == Process.SYSTEM_UID
|| resolveUid(resolvedPackage) != Process.INVALID_UID;
}
private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
if (attributionSource.getUid() != Binder.getCallingUid()
&& attributionSource.isTrusted(mContext)) {
return true;
}
return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null)
== PackageManager.PERMISSION_GRANTED;
}
private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
if (!edit) {
return null;
}
uidState = new UidState(uid);
mUidStates.put(uid, uidState);
}
return uidState;
}
private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
synchronized (this) {
getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
}
}
/**
* @return {@link PackageManagerInternal}
*/
private @NonNull PackageManagerInternal getPackageManagerInternal() {
if (mPackageManagerInternal == null) {
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
if (mPackageManagerInternal == null) {
throw new IllegalStateException("PackageManagerInternal not loaded");
}
return mPackageManagerInternal;
}
/**
* @return {@link PackageManagerLocal}
*/
private @NonNull PackageManagerLocal getPackageManagerLocal() {
if (mPackageManagerLocal == null) {
mPackageManagerLocal = LocalManagerRegistry.getManager(PackageManagerLocal.class);
}
if (mPackageManagerLocal == null) {
throw new IllegalStateException("PackageManagerLocal not loaded");
}
return mPackageManagerLocal;
}
/**
* @return {@link UserManagerInternal}
*/
private @NonNull UserManagerInternal getUserManagerInternal() {
if (mUserManagerInternal == null) {
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
if (mUserManagerInternal == null) {
throw new IllegalStateException("UserManagerInternal not loaded");
}
return mUserManagerInternal;
}
/**
* Create a restriction description matching the properties of the package.
*
* @param pkg The package to create the restriction description for
*
* @return The restriction matching the package
*/
private RestrictionBypass getBypassforPackage(@NonNull PackageState packageState) {
return new RestrictionBypass(packageState.getAppId() == Process.SYSTEM_UID,
packageState.isPrivileged(), mContext.checkPermission(
android.Manifest.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1,
packageState.getAppId()) == PackageManager.PERMISSION_GRANTED);
}
/**
* @see #verifyAndGetBypass(int, String, String, String, boolean)
*/
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
@Nullable String attributionTag) {
return verifyAndGetBypass(uid, packageName, attributionTag, null);
}
/**
* @see #verifyAndGetBypass(int, String, String, String, boolean)
*/
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
@Nullable String attributionTag, @Nullable String proxyPackageName) {
return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName, false);
}
/**
* Verify that package belongs to uid and return the {@link RestrictionBypass bypass
* description} for the package, along with a boolean indicating whether the attribution tag is
* valid.
*
* @param uid The uid the package belongs to
* @param packageName The package the might belong to the uid
* @param attributionTag attribution tag or {@code null} if no need to verify
* @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
* @param suppressErrorLogs Whether to print to logcat about nonmatching parameters
*
* @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
* attribution tag is valid
*/
private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
@Nullable String attributionTag, @Nullable String proxyPackageName,
boolean suppressErrorLogs) {
if (uid == Process.ROOT_UID) {
// For backwards compatibility, don't check package name for root UID.
return new PackageVerificationResult(null,
/* isAttributionTagValid */ true);
}
if (Process.isSdkSandboxUid(uid)) {
// SDK sandbox processes run in their own UID range, but their associated
// UID for checks should always be the UID of the package implementing SDK sandbox
// service.
// TODO: We will need to modify the callers of this function instead, so
// modifications and checks against the app ops state are done with the
// correct UID.
try {
final PackageManager pm = mContext.getPackageManager();
final String supplementalPackageName = pm.getSdkSandboxPackageName();
if (Objects.equals(packageName, supplementalPackageName)) {
uid = pm.getPackageUidAsUser(supplementalPackageName,
PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
}
} catch (PackageManager.NameNotFoundException e) {
// Shouldn't happen for the supplemental package
e.printStackTrace();
}
}
// Do not check if uid/packageName/attributionTag is already known.
synchronized (this) {
UidState uidState = mUidStates.get(uid);
if (uidState != null && !uidState.pkgOps.isEmpty()) {
Ops ops = uidState.pkgOps.get(packageName);
if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
attributionTag)) && ops.bypass != null) {
return new PackageVerificationResult(ops.bypass,
ops.validAttributionTags.contains(attributionTag));
}
}
}
int callingUid = Binder.getCallingUid();
// Allow any attribution tag for resolvable uids
int pkgUid;
if (Objects.equals(packageName, "com.android.shell")) {
// Special case for the shell which is a package but should be able
// to bypass app attribution tag restrictions.
pkgUid = Process.SHELL_UID;
} else {
pkgUid = resolveUid(packageName);
}
if (pkgUid != Process.INVALID_UID) {
if (pkgUid != UserHandle.getAppId(uid)) {
if (!suppressErrorLogs) {
Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ "Package \"" + packageName + "\" does not belong to uid " + uid
+ ".");
}
String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+ UserHandle.getAppId(uid) + otherUidMessage);
}
return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
/* isAttributionTagValid */ true);
}
int userId = UserHandle.getUserId(uid);
RestrictionBypass bypass = null;
boolean isAttributionTagValid = false;
final long ident = Binder.clearCallingIdentity();
try {
PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
var pkgState = pmInt.getPackageStateInternal(packageName);
var pkg = pkgState == null ? null : pkgState.getAndroidPackage();
if (pkg != null) {
isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
pkgUid = UserHandle.getUid(userId, pkgState.getAppId());
bypass = getBypassforPackage(pkgState);
}
if (!isAttributionTagValid) {
AndroidPackage proxyPkg = proxyPackageName != null
? pmInt.getPackage(proxyPackageName) : null;
// Re-check in proxy.
isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
String msg;
if (pkg != null && isAttributionTagValid) {
msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+ " package " + proxyPackageName + ", this is not advised";
} else if (pkg != null) {
msg = "attributionTag " + attributionTag + " not declared in manifest of "
+ packageName;
} else {
msg = "package " + packageName + " not found, can't check for "
+ "attributionTag " + attributionTag;
}
try {
if (!mPlatformCompat.isChangeEnabledByPackageName(
SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
userId) || !mPlatformCompat.isChangeEnabledByUid(
SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
callingUid)) {
// Do not override tags if overriding is not enabled for this package
isAttributionTagValid = true;
}
Slog.e(TAG, msg);
} catch (RemoteException neverHappens) {
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
if (pkgUid != uid) {
if (!suppressErrorLogs) {
Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
}
String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+ otherUidMessage);
}
return new PackageVerificationResult(bypass, isAttributionTagValid);
}
private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
@Nullable String attributionTag) {
if (pkg == null) {
return false;
} else if (attributionTag == null) {
return true;
}
if (pkg.getAttributions() != null) {
int numAttributions = pkg.getAttributions().size();
for (int i = 0; i < numAttributions; i++) {
if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
return true;
}
}
}
return false;
}
/**
* Get (and potentially create) ops.
*
* @param uid The uid the package belongs to
* @param packageName The name of the package
* @param attributionTag attribution tag
* @param isAttributionTagValid whether the given attribution tag is valid
* @param bypass When to bypass certain op restrictions (can be null if edit == false)
* @param edit If an ops does not exist, create the ops?
* @return The ops
*/
private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
UidState uidState = getUidStateLocked(uid, edit);
if (uidState == null) {
return null;
}
Ops ops = uidState.pkgOps.get(packageName);
if (ops == null) {
if (!edit) {
return null;
}
ops = new Ops(packageName, uidState);
uidState.pkgOps.put(packageName, ops);
}
if (edit) {
if (bypass != null) {
ops.bypass = bypass;
}
if (attributionTag != null) {
ops.knownAttributionTags.add(attributionTag);
if (isAttributionTagValid) {
ops.validAttributionTags.add(attributionTag);
} else {
ops.validAttributionTags.remove(attributionTag);
}
}
}
return ops;
}
private void scheduleWriteLocked() {
if (!mWriteScheduled) {
mWriteScheduled = true;
mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
}
}
private void scheduleFastWriteLocked() {
if (!mFastWriteScheduled) {
mWriteScheduled = true;
mFastWriteScheduled = true;
mHandler.removeCallbacks(mWriteRunner);
mHandler.postDelayed(mWriteRunner, 10*1000);
}
}
/**
* Get the state of an op for a uid.
*
* @param code The code of the op
* @param uid The uid the of the package
* @param packageName The package name for which to get the state for
* @param attributionTag The attribution tag
* @param isAttributionTagValid Whether the given attribution tag is valid
* @param bypass When to bypass certain op restrictions (can be null if edit == false)
* @param edit Iff {@code true} create the {@link Op} object if not yet created
*
* @return The {@link Op state} of the op
*/
private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, boolean isAttributionTagValid,
@Nullable RestrictionBypass bypass, boolean edit) {
Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
edit);
if (ops == null) {
return null;
}
return getOpLocked(ops, code, uid, edit);
}
private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
Op op = ops.get(code);
if (op == null) {
if (!edit) {
return null;
}
op = new Op(ops.uidState, ops.packageName, code, uid);
ops.put(code, op);
}
if (edit) {
scheduleWriteLocked();
}
return op;
}
private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
return false;
}
final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
}
private boolean isOpRestrictedLocked(int uid, int code, String packageName,
String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
int restrictionSetCount = mOpGlobalRestrictions.size();
for (int i = 0; i < restrictionSetCount; i++) {
ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
if (restrictionState.hasRestriction(code)) {
return true;
}
}
int userHandle = UserHandle.getUserId(uid);
restrictionSetCount = mOpUserRestrictions.size();
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.
ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
isCheckOp)) {
RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
if (opBypass != null) {
// If we are the system, bypass user restrictions for certain codes
synchronized (this) {
if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
return false;
}
if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
return false;
}
if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
&& appBypass.isRecordAudioRestrictionExcept) {
return false;
}
}
}
return true;
}
}
return false;
}
/**
* Read recent accesses from persistence (mRecentAccessesFile).
* If there is no mRecentAccessesFile yet, we'll need migrate from mStorageFile: first read from
* mStorageFile, then all subsequent reads/writes will use mRecentAccessesFile.
* If neither file exists, there's nothing to migrate.
*/
private void readRecentAccesses() {
if (!mRecentAccessesFile.exists()) {
readRecentAccesses(mStorageFile);
} else {
readRecentAccesses(mRecentAccessesFile);
}
}
private void readRecentAccesses(AtomicFile file) {
synchronized (file) {
synchronized (this) {
FileInputStream stream;
try {
stream = file.openRead();
} catch (FileNotFoundException e) {
Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
return;
}
boolean success = false;
mUidStates.clear();
mAppOpsCheckingService.clearAllModes();
try {
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Parse next until we reach the start or end
}
if (type != XmlPullParser.START_TAG) {
throw new IllegalStateException("no start tag found");
}
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("pkg")) {
readPackage(parser);
} else if (tagName.equals("uid")) {
// uid tag may be present during migration, don't print warning.
XmlUtils.skipCurrentTag(parser);
} else {
Slog.w(TAG, "Unknown element under <app-ops>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
success = true;
} catch (IllegalStateException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (NullPointerException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (NumberFormatException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (XmlPullParserException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (IOException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (IndexOutOfBoundsException e) {
Slog.w(TAG, "Failed parsing " + e);
} finally {
if (!success) {
mUidStates.clear();
mAppOpsCheckingService.clearAllModes();
}
try {
stream.close();
} catch (IOException e) {
}
}
}
}
}
private void readPackage(TypedXmlPullParser parser)
throws NumberFormatException, XmlPullParserException, IOException {
String pkgName = parser.getAttributeValue(null, "n");
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("uid")) {
readUid(parser, pkgName);
} else {
Slog.w(TAG, "Unknown element under <pkg>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
}
private void readUid(TypedXmlPullParser parser, String pkgName)
throws NumberFormatException, XmlPullParserException, IOException {
int uid = parser.getAttributeInt(null, "n");
final UidState uidState = getUidStateLocked(uid, true);
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("op")) {
readOp(parser, uidState, pkgName);
} else {
Slog.w(TAG, "Unknown element under <pkg>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
}
private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
@Nullable String attribution)
throws NumberFormatException, IOException, XmlPullParserException {
final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
final long key = parser.getAttributeLong(null, "n");
final int uidState = extractUidStateFromKey(key);
final int opFlags = extractFlagsFromKey(key);
final long accessTime = parser.getAttributeLong(null, "t", 0);
final long rejectTime = parser.getAttributeLong(null, "r", 0);
final long accessDuration = parser.getAttributeLong(null, "d", -1);
final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
if (accessTime > 0) {
attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
proxyAttributionTag, uidState, opFlags);
}
if (rejectTime > 0) {
attributedOp.rejected(rejectTime, uidState, opFlags);
}
}
private void readOp(TypedXmlPullParser parser,
@NonNull UidState uidState, @NonNull String pkgName)
throws NumberFormatException, XmlPullParserException, IOException {
int opCode = parser.getAttributeInt(null, "n");
Op op = new Op(uidState, pkgName, opCode, uidState.uid);
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("st")) {
readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
} else {
Slog.w(TAG, "Unknown element under <op>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
Ops ops = uidState.pkgOps.get(pkgName);
if (ops == null) {
ops = new Ops(pkgName, uidState);
uidState.pkgOps.put(pkgName, ops);
}
ops.put(op.op, op);
}
@VisibleForTesting
void writeRecentAccesses() {
synchronized (mRecentAccessesFile) {
FileOutputStream stream;
try {
stream = mRecentAccessesFile.startWrite();
} catch (IOException e) {
Slog.w(TAG, "Failed to write state: " + e);
return;
}
List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
try {
TypedXmlSerializer out = Xml.resolveSerializer(stream);
out.startDocument(null, true);
out.startTag(null, "app-ops");
out.attributeInt(null, "v", CURRENT_VERSION);
if (allOps != null) {
String lastPkg = null;
for (int i=0; i<allOps.size(); i++) {
AppOpsManager.PackageOps pkg = allOps.get(i);
if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
if (lastPkg != null) {
out.endTag(null, "pkg");
}
lastPkg = pkg.getPackageName();
if (lastPkg != null) {
out.startTag(null, "pkg");
out.attribute(null, "n", lastPkg);
}
}
out.startTag(null, "uid");
out.attributeInt(null, "n", pkg.getUid());
List<AppOpsManager.OpEntry> ops = pkg.getOps();
for (int j=0; j<ops.size(); j++) {
AppOpsManager.OpEntry op = ops.get(j);
out.startTag(null, "op");
out.attributeInt(null, "n", op.getOp());
if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
out.attributeInt(null, "m", op.getMode());
}
for (String attributionTag : op.getAttributedOpEntries().keySet()) {
final AttributedOpEntry attribution =
op.getAttributedOpEntries().get(attributionTag);
final ArraySet<Long> keys = attribution.collectKeys();
final int keyCount = keys.size();
for (int k = 0; k < keyCount; k++) {
final long key = keys.valueAt(k);
final int uidState = AppOpsManager.extractUidStateFromKey(key);
final int flags = AppOpsManager.extractFlagsFromKey(key);
final long accessTime = attribution.getLastAccessTime(uidState,
uidState, flags);
final long rejectTime = attribution.getLastRejectTime(uidState,
uidState, flags);
final long accessDuration = attribution.getLastDuration(
uidState, uidState, flags);
// Proxy information for rejections is not backed up
final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
uidState, uidState, flags);
if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
&& proxy == null) {
continue;
}
String proxyPkg = null;
String proxyAttributionTag = null;
int proxyUid = Process.INVALID_UID;
if (proxy != null) {
proxyPkg = proxy.getPackageName();
proxyAttributionTag = proxy.getAttributionTag();
proxyUid = proxy.getUid();
}
out.startTag(null, "st");
if (attributionTag != null) {
out.attribute(null, "id", attributionTag);
}
out.attributeLong(null, "n", key);
if (accessTime > 0) {
out.attributeLong(null, "t", accessTime);
}
if (rejectTime > 0) {
out.attributeLong(null, "r", rejectTime);
}
if (accessDuration > 0) {
out.attributeLong(null, "d", accessDuration);
}
if (proxyPkg != null) {
out.attribute(null, "pp", proxyPkg);
}
if (proxyAttributionTag != null) {
out.attribute(null, "pc", proxyAttributionTag);
}
if (proxyUid >= 0) {
out.attributeInt(null, "pu", proxyUid);
}
out.endTag(null, "st");
}
}
out.endTag(null, "op");
}
out.endTag(null, "uid");
}
if (lastPkg != null) {
out.endTag(null, "pkg");
}
}
out.endTag(null, "app-ops");
out.endDocument();
mRecentAccessesFile.finishWrite(stream);
} catch (IOException e) {
Slog.w(TAG, "Failed to write state, restoring backup.", e);
mRecentAccessesFile.failWrite(stream);
}
}
mHistoricalRegistry.writeAndClearDiscreteHistory();
}
static class Shell extends ShellCommand {
final IAppOpsService mInterface;
final AppOpsService mInternal;
int userId = UserHandle.USER_SYSTEM;
String packageName;
String attributionTag;
String opStr;
String modeStr;
int op;
int mode;
int packageUid;
int nonpackageUid;
final static Binder sBinder = new Binder();
IBinder mToken;
boolean targetsUid;
Shell(IAppOpsService iface, AppOpsService internal) {
mInterface = iface;
mInternal = internal;
mToken = AppOpsManager.getClientId();
}
@Override
public int onCommand(String cmd) {
return onShellCommand(this, cmd);
}
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
dumpCommandHelp(pw);
}
static private int strOpToOp(String op, PrintWriter err) {
try {
return AppOpsManager.strOpToOp(op);
} catch (IllegalArgumentException e) {
}
try {
return Integer.parseInt(op);
} catch (NumberFormatException e) {
}
try {
return AppOpsManager.strDebugOpToOp(op);
} catch (IllegalArgumentException e) {
err.println("Error: " + e.getMessage());
return -1;
}
}
static int strModeToMode(String modeStr, PrintWriter err) {
for (int i = AppOpsManager.MODE_NAMES.length - 1; i >= 0; i--) {
if (AppOpsManager.MODE_NAMES[i].equals(modeStr)) {
return i;
}
}
try {
return Integer.parseInt(modeStr);
} catch (NumberFormatException e) {
}
err.println("Error: Mode " + modeStr + " is not valid");
return -1;
}
int parseUserOpMode(int defMode, PrintWriter err) throws RemoteException {
userId = UserHandle.USER_CURRENT;
opStr = null;
modeStr = null;
for (String argument; (argument = getNextArg()) != null;) {
if ("--user".equals(argument)) {
userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
if (opStr == null) {
opStr = argument;
} else if (modeStr == null) {
modeStr = argument;
break;
}
}
}
if (opStr == null) {
err.println("Error: Operation not specified.");
return -1;
}
op = strOpToOp(opStr, err);
if (op < 0) {
return -1;
}
if (modeStr != null) {
if ((mode=strModeToMode(modeStr, err)) < 0) {
return -1;
}
} else {
mode = defMode;
}
return 0;
}
int parseUserPackageOp(boolean reqOp, PrintWriter err) throws RemoteException {
userId = UserHandle.USER_CURRENT;
packageName = null;
opStr = null;
for (String argument; (argument = getNextArg()) != null;) {
if ("--user".equals(argument)) {
userId = UserHandle.parseUserArg(getNextArgRequired());
} else if ("--uid".equals(argument)) {
targetsUid = true;
} else if ("--attribution".equals(argument)) {
attributionTag = getNextArgRequired();
} else {
if (packageName == null) {
packageName = argument;
} else if (opStr == null) {
opStr = argument;
break;
}
}
}
if (packageName == null) {
err.println("Error: Package name not specified.");
return -1;
} else if (opStr == null && reqOp) {
err.println("Error: Operation not specified.");
return -1;
}
if (opStr != null) {
op = strOpToOp(opStr, err);
if (op < 0) {
return -1;
}
} else {
op = AppOpsManager.OP_NONE;
}
if (userId == UserHandle.USER_CURRENT) {
userId = ActivityManager.getCurrentUser();
}
nonpackageUid = -1;
try {
nonpackageUid = Integer.parseInt(packageName);
} catch (NumberFormatException e) {
}
if (nonpackageUid == -1 && packageName.length() > 1 && packageName.charAt(0) == 'u'
&& packageName.indexOf('.') < 0) {
int i = 1;
while (i < packageName.length() && packageName.charAt(i) >= '0'
&& packageName.charAt(i) <= '9') {
i++;
}
if (i > 1 && i < packageName.length()) {
String userStr = packageName.substring(1, i);
try {
int user = Integer.parseInt(userStr);
char type = packageName.charAt(i);
i++;
int startTypeVal = i;
while (i < packageName.length() && packageName.charAt(i) >= '0'
&& packageName.charAt(i) <= '9') {
i++;
}
if (i > startTypeVal) {
String typeValStr = packageName.substring(startTypeVal, i);
try {
int typeVal = Integer.parseInt(typeValStr);
if (type == 'a') {
nonpackageUid = UserHandle.getUid(user,
typeVal + Process.FIRST_APPLICATION_UID);
} else if (type == 's') {
nonpackageUid = UserHandle.getUid(user, typeVal);
}
} catch (NumberFormatException e) {
}
}
} catch (NumberFormatException e) {
}
}
}
if (nonpackageUid != -1) {
packageName = null;
} else {
packageUid = resolveUid(packageName);
if (packageUid < 0) {
packageUid = AppGlobals.getPackageManager().getPackageUid(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
}
if (packageUid < 0) {
err.println("Error: No UID for " + packageName + " in user " + userId);
return -1;
}
}
return 0;
}
}
@Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new Shell(this, this)).exec(this, in, out, err, args, callback, resultReceiver);
}
static void dumpCommandHelp(PrintWriter pw) {
pw.println("AppOps service (appops) commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" start [--user <USER_ID>] [--attribution <ATTRIBUTION_TAG>] <PACKAGE | UID> "
+ "<OP> ");
pw.println(" Starts a given operation for a particular application.");
pw.println(" stop [--user <USER_ID>] [--attribution <ATTRIBUTION_TAG>] <PACKAGE | UID> "
+ "<OP> ");
pw.println(" Stops a given operation for a particular application.");
pw.println(" set [--user <USER_ID>] <[--uid] PACKAGE | UID> <OP> <MODE>");
pw.println(" Set the mode for a particular application and operation.");
pw.println(" get [--user <USER_ID>] [--attribution <ATTRIBUTION_TAG>] <PACKAGE | UID> "
+ "[<OP>]");
pw.println(" Return the mode for a particular application and optional operation.");
pw.println(" query-op [--user <USER_ID>] <OP> [<MODE>]");
pw.println(" Print all packages that currently have the given op in the given mode.");
pw.println(" reset [--user <USER_ID>] [<PACKAGE>]");
pw.println(" Reset the given application or all applications to default modes.");
pw.println(" write-settings");
pw.println(" Immediately write pending changes to storage.");
pw.println(" read-settings");
pw.println(" Read the last written settings, replacing current state in RAM.");
pw.println(" options:");
pw.println(" <PACKAGE> an Android package name or its UID if prefixed by --uid");
pw.println(" <OP> an AppOps operation.");
pw.println(" <MODE> one of allow, ignore, deny, or default");
pw.println(" <USER_ID> the user id under which the package is installed. If --user is");
pw.println(" not specified, the current user is assumed.");
}
static int onShellCommand(Shell shell, String cmd) {
if (cmd == null) {
return shell.handleDefaultCommands(cmd);
}
PrintWriter pw = shell.getOutPrintWriter();
PrintWriter err = shell.getErrPrintWriter();
try {
switch (cmd) {
case "set": {
int res = shell.parseUserPackageOp(true, err);
if (res < 0) {
return res;
}
String modeStr = shell.getNextArg();
if (modeStr == null) {
err.println("Error: Mode not specified.");
return -1;
}
final int mode = shell.strModeToMode(modeStr, err);
if (mode < 0) {
return -1;
}
if (!shell.targetsUid && shell.packageName != null) {
shell.mInterface.setMode(shell.op, shell.packageUid, shell.packageName,
mode);
} else if (shell.targetsUid && shell.packageName != null) {
try {
final int uid = shell.mInternal.mContext.getPackageManager()
.getPackageUidAsUser(shell.packageName, shell.userId);
shell.mInterface.setUidMode(shell.op, uid, mode);
} catch (PackageManager.NameNotFoundException e) {
return -1;
}
} else {
shell.mInterface.setUidMode(shell.op, shell.nonpackageUid, mode);
}
return 0;
}
case "get": {
int res = shell.parseUserPackageOp(false, err);
if (res < 0) {
return res;
}
List<AppOpsManager.PackageOps> ops = new ArrayList<>();
if (shell.packageName != null) {
// Uid mode overrides package mode, so make sure it's also reported
List<AppOpsManager.PackageOps> r = shell.mInterface.getUidOps(
shell.packageUid,
shell.op != AppOpsManager.OP_NONE ? new int[]{shell.op} : null);
if (r != null) {
ops.addAll(r);
}
r = shell.mInterface.getOpsForPackage(
shell.packageUid, shell.packageName,
shell.op != AppOpsManager.OP_NONE ? new int[]{shell.op} : null);
if (r != null) {
ops.addAll(r);
}
} else {
ops = shell.mInterface.getUidOps(
shell.nonpackageUid,
shell.op != AppOpsManager.OP_NONE ? new int[]{shell.op} : null);
}
if (ops == null || ops.size() <= 0) {
pw.println("No operations.");
if (shell.op > AppOpsManager.OP_NONE && shell.op < AppOpsManager._NUM_OP) {
pw.println("Default mode: " + AppOpsManager.modeToName(
AppOpsManager.opToDefaultMode(shell.op)));
}
return 0;
}
final long now = System.currentTimeMillis();
for (int i=0; i<ops.size(); i++) {
AppOpsManager.PackageOps packageOps = ops.get(i);
if (packageOps.getPackageName() == null) {
pw.print("Uid mode: ");
}
List<AppOpsManager.OpEntry> entries = packageOps.getOps();
for (int j=0; j<entries.size(); j++) {
AppOpsManager.OpEntry ent = entries.get(j);
pw.print(AppOpsManager.opToName(ent.getOp()));
pw.print(": ");
pw.print(AppOpsManager.modeToName(ent.getMode()));
if (shell.attributionTag == null) {
if (ent.getLastAccessTime(OP_FLAGS_ALL) != -1) {
pw.print("; time=");
TimeUtils.formatDuration(
now - ent.getLastAccessTime(OP_FLAGS_ALL), pw);
pw.print(" ago");
}
if (ent.getLastRejectTime(OP_FLAGS_ALL) != -1) {
pw.print("; rejectTime=");
TimeUtils.formatDuration(
now - ent.getLastRejectTime(OP_FLAGS_ALL), pw);
pw.print(" ago");
}
if (ent.isRunning()) {
pw.print(" (running)");
} else if (ent.getLastDuration(OP_FLAGS_ALL) != -1) {
pw.print("; duration=");
TimeUtils.formatDuration(ent.getLastDuration(OP_FLAGS_ALL), pw);
}
} else {
final AppOpsManager.AttributedOpEntry attributionEnt =
ent.getAttributedOpEntries().get(shell.attributionTag);
if (attributionEnt != null) {
if (attributionEnt.getLastAccessTime(OP_FLAGS_ALL) != -1) {
pw.print("; time=");
TimeUtils.formatDuration(
now - attributionEnt.getLastAccessTime(
OP_FLAGS_ALL), pw);
pw.print(" ago");
}
if (attributionEnt.getLastRejectTime(OP_FLAGS_ALL) != -1) {
pw.print("; rejectTime=");
TimeUtils.formatDuration(
now - attributionEnt.getLastRejectTime(
OP_FLAGS_ALL), pw);
pw.print(" ago");
}
if (attributionEnt.isRunning()) {
pw.print(" (running)");
} else if (attributionEnt.getLastDuration(OP_FLAGS_ALL)
!= -1) {
pw.print("; duration=");
TimeUtils.formatDuration(
attributionEnt.getLastDuration(OP_FLAGS_ALL), pw);
}
}
}
pw.println();
}
}
return 0;
}
case "query-op": {
int res = shell.parseUserOpMode(AppOpsManager.MODE_IGNORED, err);
if (res < 0) {
return res;
}
List<AppOpsManager.PackageOps> ops = shell.mInterface.getPackagesForOps(
new int[] {shell.op});
if (ops == null || ops.size() <= 0) {
pw.println("No operations.");
return 0;
}
for (int i=0; i<ops.size(); i++) {
final AppOpsManager.PackageOps pkg = ops.get(i);
boolean hasMatch = false;
final List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
for (int j=0; j<entries.size(); j++) {
AppOpsManager.OpEntry ent = entries.get(j);
if (ent.getOp() == shell.op && ent.getMode() == shell.mode) {
hasMatch = true;
break;
}
}
if (hasMatch) {
pw.println(pkg.getPackageName());
}
}
return 0;
}
case "reset": {
String packageName = null;
int userId = UserHandle.USER_CURRENT;
for (String argument; (argument = shell.getNextArg()) != null;) {
if ("--user".equals(argument)) {
String userStr = shell.getNextArgRequired();
userId = UserHandle.parseUserArg(userStr);
} else {
if (packageName == null) {
packageName = argument;
} else {
err.println("Error: Unsupported argument: " + argument);
return -1;
}
}
}
if (userId == UserHandle.USER_CURRENT) {
userId = ActivityManager.getCurrentUser();
}
shell.mInterface.resetAllModes(userId, packageName);
pw.print("Reset all modes for: ");
if (userId == UserHandle.USER_ALL) {
pw.print("all users");
} else {
pw.print("user "); pw.print(userId);
}
pw.print(", ");
if (packageName == null) {
pw.println("all packages");
} else {
pw.print("package "); pw.println(packageName);
}
return 0;
}
case "write-settings": {
shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
synchronized (shell.mInternal) {
shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
}
shell.mInternal.writeRecentAccesses();
shell.mInternal.mAppOpsCheckingService.writeState();
pw.println("Current settings written.");
} finally {
Binder.restoreCallingIdentity(token);
}
return 0;
}
case "read-settings": {
shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
shell.mInternal.readRecentAccesses();
shell.mInternal.mAppOpsCheckingService.readState();
pw.println("Last settings read.");
} finally {
Binder.restoreCallingIdentity(token);
}
return 0;
}
case "start": {
int res = shell.parseUserPackageOp(true, err);
if (res < 0) {
return res;
}
if (shell.packageName != null) {
shell.mInterface.startOperation(shell.mToken, shell.op, shell.packageUid,
shell.packageName, shell.attributionTag, true, true,
"appops start shell command", true,
AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, ATTRIBUTION_CHAIN_ID_NONE);
} else {
return -1;
}
return 0;
}
case "stop": {
int res = shell.parseUserPackageOp(true, err);
if (res < 0) {
return res;
}
if (shell.packageName != null) {
shell.mInterface.finishOperation(shell.mToken, shell.op, shell.packageUid,
shell.packageName, shell.attributionTag);
} else {
return -1;
}
return 0;
}
default:
return shell.handleDefaultCommands(cmd);
}
} catch (RemoteException e) {
pw.println("Remote exception: " + e);
}
return -1;
}
private void dumpHelp(PrintWriter pw) {
pw.println("AppOps service (appops) dump options:");
pw.println(" -h");
pw.println(" Print this help text.");
pw.println(" --op [OP]");
pw.println(" Limit output to data associated with the given app op code.");
pw.println(" --mode [MODE]");
pw.println(" Limit output to data associated with the given app op mode.");
pw.println(" --package [PACKAGE]");
pw.println(" Limit output to data associated with the given package name.");
pw.println(" --attributionTag [attributionTag]");
pw.println(" Limit output to data associated with the given attribution tag.");
pw.println(" --include-discrete [n]");
pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit.");
pw.println(" --watchers");
pw.println(" Only output the watcher sections.");
pw.println(" --history");
pw.println(" Only output history.");
pw.println(" --uid-state-changes");
pw.println(" Include logs about uid state changes.");
}
private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
@NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
final int numAttributions = op.mAttributions.size();
for (int i = 0; i < numAttributions; i++) {
if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
op.mAttributions.keyAt(i), filterAttributionTag)) {
continue;
}
pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
prefix + " ");
pw.print(prefix + "]\n");
}
}
private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
@Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
@NonNull Date date, @NonNull String prefix) {
final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
attributionTag).getAttributedOpEntries().get(attributionTag);
final ArraySet<Long> keys = entry.collectKeys();
final int keyCount = keys.size();
for (int k = 0; k < keyCount; k++) {
final long key = keys.valueAt(k);
final int uidState = AppOpsManager.extractUidStateFromKey(key);
final int flags = AppOpsManager.extractFlagsFromKey(key);
final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
String proxyPkg = null;
String proxyAttributionTag = null;
int proxyUid = Process.INVALID_UID;
if (proxy != null) {
proxyPkg = proxy.getPackageName();
proxyAttributionTag = proxy.getAttributionTag();
proxyUid = proxy.getUid();
}
if (accessTime > 0) {
pw.print(prefix);
pw.print("Access: ");
pw.print(AppOpsManager.keyToString(key));
pw.print(" ");
date.setTime(accessTime);
pw.print(sdf.format(date));
pw.print(" (");
TimeUtils.formatDuration(accessTime - now, pw);
pw.print(")");
if (accessDuration > 0) {
pw.print(" duration=");
TimeUtils.formatDuration(accessDuration, pw);
}
if (proxyUid >= 0) {
pw.print(" proxy[");
pw.print("uid=");
pw.print(proxyUid);
pw.print(", pkg=");
pw.print(proxyPkg);
pw.print(", attributionTag=");
pw.print(proxyAttributionTag);
pw.print("]");
}
pw.println();
}
if (rejectTime > 0) {
pw.print(prefix);
pw.print("Reject: ");
pw.print(AppOpsManager.keyToString(key));
date.setTime(rejectTime);
pw.print(sdf.format(date));
pw.print(" (");
TimeUtils.formatDuration(rejectTime - now, pw);
pw.print(")");
if (proxyUid >= 0) {
pw.print(" proxy[");
pw.print("uid=");
pw.print(proxyUid);
pw.print(", pkg=");
pw.print(proxyPkg);
pw.print(", attributionTag=");
pw.print(proxyAttributionTag);
pw.print("]");
}
pw.println();
}
}
final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
if (attributedOp.isRunning()) {
long earliestElapsedTime = Long.MAX_VALUE;
long maxNumStarts = 0;
int numInProgressEvents = attributedOp.mInProgressEvents.size();
for (int i = 0; i < numInProgressEvents; i++) {
AttributedOp.InProgressStartOpEvent event =
attributedOp.mInProgressEvents.valueAt(i);
earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
}
pw.print(prefix + "Running start at: ");
TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
pw.println();
if (maxNumStarts > 1) {
pw.print(prefix + "startNesting=");
pw.println(maxNumStarts);
}
}
}
@NeverCompile // Avoid size overhead of debugging code.
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
int dumpOp = OP_NONE;
String dumpPackage = null;
String dumpAttributionTag = null;
int dumpUid = Process.INVALID_UID;
int dumpMode = -1;
boolean dumpWatchers = false;
// TODO ntmyren: Remove the dumpHistory and dumpFilter
boolean dumpHistory = false;
boolean includeDiscreteOps = false;
boolean dumpUidStateChangeLogs = false;
int nDiscreteOps = 10;
@HistoricalOpsRequestFilter int dumpFilter = 0;
boolean dumpAll = false;
if (args != null) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if ("-h".equals(arg)) {
dumpHelp(pw);
return;
} else if ("-a".equals(arg)) {
// dump all data
dumpAll = true;
} else if ("--op".equals(arg)) {
i++;
if (i >= args.length) {
pw.println("No argument for --op option");
return;
}
dumpOp = Shell.strOpToOp(args[i], pw);
dumpFilter |= FILTER_BY_OP_NAMES;
if (dumpOp < 0) {
return;
}
} else if ("--package".equals(arg)) {
i++;
if (i >= args.length) {
pw.println("No argument for --package option");
return;
}
dumpPackage = args[i];
dumpFilter |= FILTER_BY_PACKAGE_NAME;
try {
dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
0);
} catch (RemoteException e) {
}
if (dumpUid < 0) {
pw.println("Unknown package: " + dumpPackage);
return;
}
dumpUid = UserHandle.getAppId(dumpUid);
dumpFilter |= FILTER_BY_UID;
} else if ("--attributionTag".equals(arg)) {
i++;
if (i >= args.length) {
pw.println("No argument for --attributionTag option");
return;
}
dumpAttributionTag = args[i];
dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
} else if ("--mode".equals(arg)) {
i++;
if (i >= args.length) {
pw.println("No argument for --mode option");
return;
}
dumpMode = Shell.strModeToMode(args[i], pw);
if (dumpMode < 0) {
return;
}
} else if ("--watchers".equals(arg)) {
dumpWatchers = true;
} else if ("--include-discrete".equals(arg)) {
i++;
if (i >= args.length) {
pw.println("No argument for --include-discrete option");
return;
}
try {
nDiscreteOps = Integer.valueOf(args[i]);
} catch (NumberFormatException e) {
pw.println("Wrong parameter: " + args[i]);
return;
}
includeDiscreteOps = true;
} else if ("--history".equals(arg)) {
dumpHistory = true;
} else if (arg.length() > 0 && arg.charAt(0) == '-') {
pw.println("Unknown option: " + arg);
return;
} else if ("--uid-state-changes".equals(arg)) {
dumpUidStateChangeLogs = true;
} else {
pw.println("Unknown command: " + arg);
return;
}
}
}
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
final Date date = new Date();
synchronized (this) {
pw.println("Current AppOps Service state:");
if (!dumpHistory && !dumpWatchers) {
mConstants.dump(pw);
}
pw.println();
final long now = System.currentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final long nowUptime = SystemClock.uptimeMillis();
boolean needSep = false;
if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
&& !dumpHistory) {
pw.println(" Profile owners:");
for (int poi = 0; poi < mProfileOwners.size(); poi++) {
pw.print(" User #");
pw.print(mProfileOwners.keyAt(poi));
pw.print(": ");
UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
pw.println();
}
pw.println();
}
if (!dumpHistory) {
needSep |= mAppOpsCheckingService.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
}
if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
boolean printedHeader = false;
for (int i = 0; i < mModeWatchers.size(); i++) {
final ModeCallback cb = mModeWatchers.valueAt(i);
if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
continue;
}
needSep = true;
if (!printedHeader) {
pw.println(" All op mode watchers:");
printedHeader = true;
}
pw.print(" ");
pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
pw.print(": "); pw.println(cb);
}
}
if (mActiveWatchers.size() > 0 && dumpMode < 0) {
needSep = true;
boolean printedHeader = false;
for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
final SparseArray<ActiveCallback> activeWatchers =
mActiveWatchers.valueAt(watcherNum);
if (activeWatchers.size() <= 0) {
continue;
}
final ActiveCallback cb = activeWatchers.valueAt(0);
if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
continue;
}
if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
if (!printedHeader) {
pw.println(" All op active watchers:");
printedHeader = true;
}
pw.print(" ");
pw.print(Integer.toHexString(System.identityHashCode(
mActiveWatchers.keyAt(watcherNum))));
pw.println(" ->");
pw.print(" [");
final int opCount = activeWatchers.size();
for (int opNum = 0; opNum < opCount; opNum++) {
if (opNum > 0) {
pw.print(' ');
}
pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
if (opNum < opCount - 1) {
pw.print(',');
}
}
pw.println("]");
pw.print(" ");
pw.println(cb);
}
}
if (mStartedWatchers.size() > 0 && dumpMode < 0) {
needSep = true;
boolean printedHeader = false;
final int watchersSize = mStartedWatchers.size();
for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
final SparseArray<StartedCallback> startedWatchers =
mStartedWatchers.valueAt(watcherNum);
if (startedWatchers.size() <= 0) {
continue;
}
final StartedCallback cb = startedWatchers.valueAt(0);
if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
continue;
}
if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
if (!printedHeader) {
pw.println(" All op started watchers:");
printedHeader = true;
}
pw.print(" ");
pw.print(Integer.toHexString(System.identityHashCode(
mStartedWatchers.keyAt(watcherNum))));
pw.println(" ->");
pw.print(" [");
final int opCount = startedWatchers.size();
for (int opNum = 0; opNum < opCount; opNum++) {
if (opNum > 0) {
pw.print(' ');
}
pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
if (opNum < opCount - 1) {
pw.print(',');
}
}
pw.println("]");
pw.print(" ");
pw.println(cb);
}
}
if (mNotedWatchers.size() > 0 && dumpMode < 0) {
needSep = true;
boolean printedHeader = false;
for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
final SparseArray<NotedCallback> notedWatchers =
mNotedWatchers.valueAt(watcherNum);
if (notedWatchers.size() <= 0) {
continue;
}
final NotedCallback cb = notedWatchers.valueAt(0);
if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
continue;
}
if (dumpPackage != null
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
continue;
}
if (!printedHeader) {
pw.println(" All op noted watchers:");
printedHeader = true;
}
pw.print(" ");
pw.print(Integer.toHexString(System.identityHashCode(
mNotedWatchers.keyAt(watcherNum))));
pw.println(" ->");
pw.print(" [");
final int opCount = notedWatchers.size();
for (int opNum = 0; opNum < opCount; opNum++) {
if (opNum > 0) {
pw.print(' ');
}
pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
if (opNum < opCount - 1) {
pw.print(',');
}
}
pw.println("]");
pw.print(" ");
pw.println(cb);
}
}
if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
&& dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
needSep = mAudioRestrictionManager.dump(pw) || needSep;
}
if (needSep) {
pw.println();
}
for (int i=0; i<mUidStates.size(); i++) {
UidState uidState = mUidStates.valueAt(i);
final SparseIntArray opModes = uidState.getNonDefaultUidModes();
final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
if (dumpWatchers || dumpHistory) {
continue;
}
if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
boolean hasOp = dumpOp < 0 || (opModes != null
&& opModes.indexOfKey(dumpOp) >= 0);
boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
boolean hasMode = dumpMode < 0;
if (!hasMode && opModes != null) {
for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
if (opModes.valueAt(opi) == dumpMode) {
hasMode = true;
}
}
}
if (pkgOps != null) {
for (int pkgi = 0;
(!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
pkgi++) {
Ops ops = pkgOps.valueAt(pkgi);
if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
hasOp = true;
}
if (!hasMode) {
for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
if (ops.valueAt(opi).getMode() == dumpMode) {
hasMode = true;
}
}
}
if (!hasPackage && dumpPackage.equals(ops.packageName)) {
hasPackage = true;
}
}
}
if (uidState.foregroundOps != null && !hasOp) {
if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
hasOp = true;
}
}
if (!hasOp || !hasPackage || !hasMode) {
continue;
}
}
pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
uidState.dump(pw, nowElapsed);
if (uidState.foregroundOps != null && (dumpMode < 0
|| dumpMode == AppOpsManager.MODE_FOREGROUND)) {
pw.println(" foregroundOps:");
for (int j = 0; j < uidState.foregroundOps.size(); j++) {
if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
continue;
}
pw.print(" ");
pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
pw.print(": ");
pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
}
pw.print(" hasForegroundWatchers=");
pw.println(uidState.hasForegroundWatchers);
}
needSep = true;
if (opModes != null) {
final int opModeCount = opModes.size();
for (int j = 0; j < opModeCount; j++) {
final int code = opModes.keyAt(j);
final int mode = opModes.valueAt(j);
if (dumpOp >= 0 && dumpOp != code) {
continue;
}
if (dumpMode >= 0 && dumpMode != mode) {
continue;
}
pw.print(" "); pw.print(AppOpsManager.opToName(code));
pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
}
}
if (pkgOps == null) {
continue;
}
for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
final Ops ops = pkgOps.valueAt(pkgi);
if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
continue;
}
boolean printedPackage = false;
for (int j=0; j<ops.size(); j++) {
final Op op = ops.valueAt(j);
final int opCode = op.op;
if (dumpOp >= 0 && dumpOp != opCode) {
continue;
}
if (dumpMode >= 0 && dumpMode != op.getMode()) {
continue;
}
if (!printedPackage) {
pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
printedPackage = true;
}
pw.print(" "); pw.print(AppOpsManager.opToName(opCode));
pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
final int switchOp = AppOpsManager.opToSwitch(opCode);
if (switchOp != opCode) {
pw.print(" / switch ");
pw.print(AppOpsManager.opToName(switchOp));
final Op switchObj = ops.get(switchOp);
int mode = switchObj == null
? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
pw.print("="); pw.print(AppOpsManager.modeToName(mode));
}
pw.println("): ");
dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
sdf, date, " ");
}
}
}
if (needSep) {
pw.println();
}
boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
if (!dumpHistory && !dumpWatchers) {
pw.println();
if (mCheckOpsDelegateDispatcher.mPolicy != null
&& mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
policy.dumpTags(pw);
} else {
pw.println(" AppOps policy not set.");
}
}
if (dumpAll || dumpUidStateChangeLogs) {
pw.println();
pw.println("Uid State Changes Event Log:");
getUidStateTracker().dumpEvents(pw);
}
}
// Must not hold the appops lock
if (dumpHistory && !dumpWatchers) {
mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
dumpFilter);
}
if (includeDiscreteOps) {
pw.println("Discrete accesses: ");
mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
}
}
@Override
public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
checkSystemUid("setUserRestrictions");
Objects.requireNonNull(restrictions);
Objects.requireNonNull(token);
for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
String restriction = AppOpsManager.opToRestriction(i);
if (restriction != null) {
setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
userHandle, null);
}
}
}
@Override
public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
PackageTagsList excludedPackageTags) {
if (Binder.getCallingPid() != Process.myPid()) {
mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
if (userHandle != UserHandle.getCallingUserId()) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission
.INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
&& mContext.checkCallingOrSelfPermission(Manifest.permission
.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+ " INTERACT_ACROSS_USERS to interact cross user ");
}
}
verifyIncomingOp(code);
Objects.requireNonNull(token);
setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
}
private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
int userHandle, PackageTagsList excludedPackageTags) {
synchronized (AppOpsService.this) {
ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
if (restrictionState == null) {
try {
restrictionState = new ClientUserRestrictionState(token);
} catch (RemoteException e) {
return;
}
mOpUserRestrictions.put(token, restrictionState);
}
if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
userHandle)) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::updateStartedOpModeForUser, this, code, restricted,
userHandle));
}
if (restrictionState.isDefault()) {
mOpUserRestrictions.remove(token);
restrictionState.destroy();
}
}
}
private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
synchronized (AppOpsService.this) {
int numUids = mUidStates.size();
for (int uidNum = 0; uidNum < numUids; uidNum++) {
int uid = mUidStates.keyAt(uidNum);
if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
continue;
}
updateStartedOpModeForUidLocked(code, restricted, uid);
}
}
}
private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
}
int numPkgOps = uidState.pkgOps.size();
for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
Ops ops = uidState.pkgOps.valueAt(pkgNum);
Op op = ops != null ? ops.get(code) : null;
if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
continue;
}
int numAttrTags = op.mAttributions.size();
for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
if (restricted && attrOp.isRunning()) {
attrOp.pause();
} else if (attrOp.isPaused()) {
attrOp.resume();
}
}
}
}
private void notifyWatchersOfChange(int code, int uid) {
final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
synchronized (this) {
modeChangedListenerSet = mAppOpsCheckingService.getOpModeChangedListeners(code);
if (modeChangedListenerSet == null) {
return;
}
}
notifyOpChanged(modeChangedListenerSet, code, uid, null);
}
@Override
public void removeUser(int userHandle) throws RemoteException {
checkSystemUid("removeUser");
synchronized (AppOpsService.this) {
final int tokenCount = mOpUserRestrictions.size();
for (int i = tokenCount - 1; i >= 0; i--) {
ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
opRestrictions.removeUser(userHandle);
}
removeUidsForUserLocked(userHandle);
}
}
@Override
public boolean isOperationActive(int code, int uid, String packageName) {
if (Binder.getCallingUid() != uid) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return false;
}
final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return false;
}
// TODO moltmann: Allow to check for attribution op activeness
synchronized (AppOpsService.this) {
Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
if (pkgOps == null) {
return false;
}
Op op = pkgOps.get(code);
if (op == null) {
return false;
}
return op.isRunning();
}
}
@Override
public boolean isProxying(int op, @NonNull String proxyPackageName,
@NonNull String proxyAttributionTag, int proxiedUid,
@NonNull String proxiedPackageName) {
Objects.requireNonNull(proxyPackageName);
Objects.requireNonNull(proxiedPackageName);
final long callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
proxiedPackageName, new int[] {op});
if (packageOps == null || packageOps.isEmpty()) {
return false;
}
final List<OpEntry> opEntries = packageOps.get(0).getOps();
if (opEntries.isEmpty()) {
return false;
}
final OpEntry opEntry = opEntries.get(0);
if (!opEntry.isRunning()) {
return false;
}
final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
return proxyInfo != null && callingUid == proxyInfo.getUid()
&& proxyPackageName.equals(proxyInfo.getPackageName())
&& Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void resetPackageOpsNoHistory(@NonNull String packageName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"resetPackageOpsNoHistory");
synchronized (AppOpsService.this) {
final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
UserHandle.getCallingUserId());
if (uid == Process.INVALID_UID) {
return;
}
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
}
Ops removedOps = uidState.pkgOps.remove(packageName);
mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
if (removedOps != null) {
scheduleFastWriteLocked();
}
}
}
@Override
public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
long baseSnapshotInterval, int compressionStep) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"setHistoryParameters");
// Must not hold the appops lock
mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
}
@Override
public void offsetHistory(long offsetMillis) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"offsetHistory");
// Must not hold the appops lock
mHistoricalRegistry.offsetHistory(offsetMillis);
mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
}
@Override
public void addHistoricalOps(HistoricalOps ops) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"addHistoricalOps");
// Must not hold the appops lock
mHistoricalRegistry.addHistoricalOps(ops);
}
@Override
public void resetHistoryParameters() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"resetHistoryParameters");
// Must not hold the appops lock
mHistoricalRegistry.resetHistoryParameters();
}
@Override
public void clearHistory() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"clearHistory");
// Must not hold the appops lock
mHistoricalRegistry.clearAllHistory();
}
@Override
public void rebootHistory(long offlineDurationMillis) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"rebootHistory");
Preconditions.checkArgument(offlineDurationMillis >= 0);
// Must not hold the appops lock
mHistoricalRegistry.shutdown();
if (offlineDurationMillis > 0) {
SystemClock.sleep(offlineDurationMillis);
}
mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
mHistoricalRegistry.systemReady(mContext.getContentResolver());
mHistoricalRegistry.persistPendingHistory();
}
/**
* Report runtime access to AppOp together with message (including stack trace)
*
* @param packageName The package which reported the op
* @param notedAppOp contains code of op and attributionTag provided by developer
* @param message Message describing AppOp access (can be stack trace)
*
* @return Config for future sampling to reduce amount of reporting
*/
@Override
public MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(
String packageName, SyncNotedAppOp notedAppOp, String message) {
int uid = Binder.getCallingUid();
Objects.requireNonNull(packageName);
synchronized (this) {
switchPackageIfBootTimeOrRarelyUsedLocked(packageName);
if (!packageName.equals(mSampledPackage)) {
return new MessageSamplingConfig(OP_NONE, 0,
Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
}
Objects.requireNonNull(notedAppOp);
Objects.requireNonNull(message);
reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName,
AppOpsManager.strOpToOp(notedAppOp.getOp()),
notedAppOp.getAttributionTag(), message);
return new MessageSamplingConfig(mSampledAppOpCode, mAcceptableLeftDistance,
Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
}
}
/**
* Report runtime access to AppOp together with message (entry point for reporting
* asynchronous access)
* @param uid Uid of the package which reported the op
* @param packageName The package which reported the op
* @param opCode Code of AppOp
* @param attributionTag FeautreId of AppOp reported
* @param message Message describing AppOp access (can be stack trace)
*/
private void reportRuntimeAppOpAccessMessageAsyncLocked(int uid,
@NonNull String packageName, int opCode, @Nullable String attributionTag,
@NonNull String message) {
switchPackageIfBootTimeOrRarelyUsedLocked(packageName);
if (!Objects.equals(mSampledPackage, packageName)) {
return;
}
reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName, opCode, attributionTag,
message);
}
/**
* Decides whether reported message is within the range of watched AppOps and picks it for
* reporting uniformly at random across all received messages.
*/
private void reportRuntimeAppOpAccessMessageInternalLocked(int uid,
@NonNull String packageName, int opCode, @Nullable String attributionTag,
@NonNull String message) {
int newLeftDistance = AppOpsManager.leftCircularDistance(opCode,
mSampledAppOpCode, _NUM_OP);
if (mAcceptableLeftDistance < newLeftDistance
&& mSamplingStrategy != SAMPLING_STRATEGY_UNIFORM_OPS) {
return;
}
if (mAcceptableLeftDistance > newLeftDistance
&& mSamplingStrategy != SAMPLING_STRATEGY_UNIFORM_OPS) {
mAcceptableLeftDistance = newLeftDistance;
mMessagesCollectedCount = 0.0f;
}
mMessagesCollectedCount += 1.0f;
if (ThreadLocalRandom.current().nextFloat() <= 1.0f / mMessagesCollectedCount) {
mCollectedRuntimePermissionMessage = new RuntimeAppOpAccessMessage(uid, opCode,
packageName, attributionTag, message, mSamplingStrategy);
}
return;
}
/** Pulls current AppOps access report and resamples package and app op to watch */
@Override
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isCallerInstrumented =
ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
if (!isCallerSystem && !isCallerInstrumented) {
return null;
}
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
RuntimeAppOpAccessMessage result;
synchronized (this) {
result = mCollectedRuntimePermissionMessage;
mCollectedRuntimePermissionMessage = null;
}
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::getPackageListAndResample,
this));
return result;
}
/**
* Checks if package is in the list of rarely used package and starts watching the new package
* to collect incoming message or if collection is happening in first minutes since boot.
* @param packageName
*/
private void switchPackageIfBootTimeOrRarelyUsedLocked(@NonNull String packageName) {
if (mSampledPackage == null) {
if (ThreadLocalRandom.current().nextFloat() < 0.5f) {
mSamplingStrategy = SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
resampleAppOpForPackageLocked(packageName, true);
}
} else if (mRarelyUsedPackages.contains(packageName)) {
mRarelyUsedPackages.remove(packageName);
if (ThreadLocalRandom.current().nextFloat() < 0.5f) {
mSamplingStrategy = SAMPLING_STRATEGY_RARELY_USED;
resampleAppOpForPackageLocked(packageName, true);
}
}
}
/** Obtains package list and resamples package and appop to watch. */
private List<String> getPackageListAndResample() {
List<String> packageNames = getPackageNamesForSampling();
synchronized (this) {
resamplePackageAndAppOpLocked(packageNames);
}
return packageNames;
}
/** Resamples package and appop to watch from the list provided. */
private void resamplePackageAndAppOpLocked(@NonNull List<String> packageNames) {
if (!packageNames.isEmpty()) {
if (ThreadLocalRandom.current().nextFloat() < 0.5f) {
mSamplingStrategy = SAMPLING_STRATEGY_UNIFORM;
resampleAppOpForPackageLocked(packageNames.get(
ThreadLocalRandom.current().nextInt(packageNames.size())), true);
} else {
mSamplingStrategy = SAMPLING_STRATEGY_UNIFORM_OPS;
resampleAppOpForPackageLocked(packageNames.get(
ThreadLocalRandom.current().nextInt(packageNames.size())), false);
}
}
}
/** Resamples appop for the chosen package and initializes sampling state */
private void resampleAppOpForPackageLocked(@NonNull String packageName, boolean pickOp) {
mMessagesCollectedCount = 0.0f;
mSampledAppOpCode = pickOp ? ThreadLocalRandom.current().nextInt(_NUM_OP) : OP_NONE;
mAcceptableLeftDistance = _NUM_OP - 1;
mSampledPackage = packageName;
}
/**
* Creates list of rarely used packages - packages which were not used over last week or
* which declared but did not use permissions over last week.
* */
private void initializeRarelyUsedPackagesList(@NonNull ArraySet<String> candidates) {
AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
List<String> runtimeAppOpsList = getRuntimeAppOpsList();
AppOpsManager.HistoricalOpsRequest histOpsRequest =
new AppOpsManager.HistoricalOpsRequest.Builder(
Math.max(Instant.now().minus(7, ChronoUnit.DAYS).toEpochMilli(), 0),
Long.MAX_VALUE).setOpNames(runtimeAppOpsList).setFlags(
OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED).build();
appOps.getHistoricalOps(histOpsRequest, AsyncTask.THREAD_POOL_EXECUTOR,
new Consumer<HistoricalOps>() {
@Override
public void accept(HistoricalOps histOps) {
int uidCount = histOps.getUidCount();
for (int uidIdx = 0; uidIdx < uidCount; uidIdx++) {
final AppOpsManager.HistoricalUidOps uidOps = histOps.getUidOpsAt(
uidIdx);
int pkgCount = uidOps.getPackageCount();
for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++) {
String packageName = uidOps.getPackageOpsAt(
pkgIdx).getPackageName();
if (!candidates.contains(packageName)) {
continue;
}
AppOpsManager.HistoricalPackageOps packageOps =
uidOps.getPackageOpsAt(pkgIdx);
if (packageOps.getOpCount() != 0) {
candidates.remove(packageName);
}
}
}
synchronized (this) {
int numPkgs = mRarelyUsedPackages.size();
for (int i = 0; i < numPkgs; i++) {
candidates.add(mRarelyUsedPackages.valueAt(i));
}
mRarelyUsedPackages = candidates;
}
}
});
}
/** List of app ops related to runtime permissions */
private List<String> getRuntimeAppOpsList() {
ArrayList<String> result = new ArrayList();
for (int i = 0; i < _NUM_OP; i++) {
if (shouldCollectNotes(i)) {
result.add(opToPublicName(i));
}
}
return result;
}
/** Returns list of packages to be used for package sampling */
private @NonNull List<String> getPackageNamesForSampling() {
List<String> packageNames = new ArrayList<>();
PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
PackageList packages = packageManagerInternal.getPackageList();
for (String packageName : packages.getPackageNames()) {
PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
if (isSamplingTarget(pkg)) {
packageNames.add(pkg.packageName);
}
}
return packageNames;
}
/** Checks whether package should be included in sampling pool */
private boolean isSamplingTarget(@Nullable PackageInfo pkg) {
if (pkg == null) {
return false;
}
String[] requestedPermissions = pkg.requestedPermissions;
if (requestedPermissions == null) {
return false;
}
for (String permission : requestedPermissions) {
PermissionInfo permissionInfo;
try {
permissionInfo = mContext.getPackageManager().getPermissionInfo(permission, 0);
} catch (PackageManager.NameNotFoundException ignored) {
continue;
}
if (permissionInfo.getProtection() == PROTECTION_DANGEROUS) {
return true;
}
}
return false;
}
@GuardedBy("this")
private void removeUidsForUserLocked(int userHandle) {
for (int i = mUidStates.size() - 1; i >= 0; --i) {
final int uid = mUidStates.keyAt(i);
if (UserHandle.getUserId(uid) == userHandle) {
mUidStates.valueAt(i).clear();
mUidStates.removeAt(i);
}
}
}
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 int resolveUid(String packageName) {
if (packageName == null) {
return Process.INVALID_UID;
}
switch (packageName) {
case "root":
return Process.ROOT_UID;
case "shell":
case "dumpstate":
return Process.SHELL_UID;
case "media":
return Process.MEDIA_UID;
case "audioserver":
return Process.AUDIOSERVER_UID;
case "cameraserver":
return Process.CAMERASERVER_UID;
}
return Process.INVALID_UID;
}
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;
}
private final class ClientUserRestrictionState implements DeathRecipient {
private final IBinder token;
ClientUserRestrictionState(IBinder token)
throws RemoteException {
token.linkToDeath(this, 0);
this.token = token;
}
public boolean setRestriction(int code, boolean restricted,
PackageTagsList excludedPackageTags, int userId) {
return mAppOpsRestrictions.setUserRestriction(token, userId, code,
restricted, excludedPackageTags);
}
public boolean hasRestriction(int code, String packageName, String attributionTag,
int userId, boolean isCheckOp) {
return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
attributionTag, isCheckOp);
}
public void removeUser(int userId) {
mAppOpsRestrictions.clearUserRestrictions(token, userId);
}
public boolean isDefault() {
return !mAppOpsRestrictions.hasUserRestrictions(token);
}
@Override
public void binderDied() {
synchronized (AppOpsService.this) {
mAppOpsRestrictions.clearUserRestrictions(token);
mOpUserRestrictions.remove(token);
destroy();
}
}
public void destroy() {
token.unlinkToDeath(this, 0);
}
}
private final class ClientGlobalRestrictionState implements DeathRecipient {
final IBinder mToken;
ClientGlobalRestrictionState(IBinder token)
throws RemoteException {
token.linkToDeath(this, 0);
this.mToken = token;
}
boolean setRestriction(int code, boolean restricted) {
return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
}
boolean hasRestriction(int code) {
return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
}
boolean isDefault() {
return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
}
@Override
public void binderDied() {
mAppOpsRestrictions.clearGlobalRestrictions(mToken);
mOpGlobalRestrictions.remove(mToken);
destroy();
}
void destroy() {
mToken.unlinkToDeath(this, 0);
}
}
private final class AppOpsManagerLocalImpl implements AppOpsManagerLocal {
@Override
public boolean isUidInForeground(int uid) {
synchronized (AppOpsService.this) {
return mUidStateTracker.isUidInForeground(uid);
}
}
}
private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
@Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
synchronized (AppOpsService.this) {
mProfileOwners = owners;
}
}
@Override
public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
boolean visible) {
AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
}
@Override
public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
@Nullable IAppOpsCallback callback) {
setUidMode(code, uid, mode, callback);
}
@Override
public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
int mode, @Nullable IAppOpsCallback callback) {
setMode(code, uid, packageName, mode, callback);
}
@Override
public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
if (Binder.getCallingPid() != Process.myPid()) {
// TODO instead of this enforcement put in AppOpsManagerInternal
throw new SecurityException("Only the system can set global restrictions");
}
synchronized (AppOpsService.this) {
ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
if (restrictionState == null) {
try {
restrictionState = new ClientGlobalRestrictionState(token);
} catch (RemoteException e) {
return;
}
mOpGlobalRestrictions.put(token, restrictionState);
}
if (restrictionState.setRestriction(code, restricted)) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
code, restricted, UserHandle.USER_ALL));
}
if (restrictionState.isDefault()) {
mOpGlobalRestrictions.remove(token);
restrictionState.destroy();
}
}
}
@Override
public int getOpRestrictionCount(int code, UserHandle user, String pkg,
String attributionTag) {
int number = 0;
synchronized (AppOpsService.this) {
int numRestrictions = mOpUserRestrictions.size();
for (int i = 0; i < numRestrictions; i++) {
if (mOpUserRestrictions.valueAt(i)
.hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
false)) {
number++;
}
}
numRestrictions = mOpGlobalRestrictions.size();
for (int i = 0; i < numRestrictions; i++) {
if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
number++;
}
}
}
return number;
}
}
/**
* Async task for writing note op stack trace, op code, package name and version to file
* More specifically, writes all the collected ops from {@link #mNoteOpCallerStacktraces}
*/
private void writeNoteOps() {
synchronized (this) {
mWriteNoteOpsScheduled = false;
}
synchronized (mNoteOpCallerStacktracesFile) {
try (FileWriter writer = new FileWriter(mNoteOpCallerStacktracesFile)) {
int numTraces = mNoteOpCallerStacktraces.size();
for (int i = 0; i < numTraces; i++) {
// Writing json formatted string into file
writer.write(mNoteOpCallerStacktraces.valueAt(i).asJson());
// Comma separation, so we can wrap the entire log as a JSON object
// when all results are collected
writer.write(",");
}
} catch (IOException e) {
Slog.w(TAG, "Failed to load opsValidation file for FileWriter", e);
}
}
}
/**
* This class represents a NoteOp Trace object amd contains the necessary fields that will
* be written to file to use for permissions data validation in JSON format
*/
@Immutable
static class NoteOpTrace {
static final String STACKTRACE = "stackTrace";
static final String OP = "op";
static final String PACKAGENAME = "packageName";
static final String VERSION = "version";
private final @NonNull String mStackTrace;
private final int mOp;
private final @Nullable String mPackageName;
private final long mVersion;
/**
* Initialize a NoteOp object using a JSON object containing the necessary fields
*
* @param jsonTrace JSON object represented as a string
*
* @return NoteOpTrace object initialized with JSON fields
*/
static NoteOpTrace fromJson(String jsonTrace) {
try {
// Re-add closing bracket which acted as a delimiter by the reader
JSONObject obj = new JSONObject(jsonTrace.concat("}"));
return new NoteOpTrace(obj.getString(STACKTRACE), obj.getInt(OP),
obj.getString(PACKAGENAME), obj.getLong(VERSION));
} catch (JSONException e) {
// Swallow error, only meant for logging ops, should not affect flow of the code
Slog.e(TAG, "Error constructing NoteOpTrace object "
+ "JSON trace format incorrect", e);
return null;
}
}
NoteOpTrace(String stackTrace, int op, String packageName, long version) {
mStackTrace = stackTrace;
mOp = op;
mPackageName = packageName;
mVersion = version;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NoteOpTrace that = (NoteOpTrace) o;
return mOp == that.mOp
&& mVersion == that.mVersion
&& mStackTrace.equals(that.mStackTrace)
&& Objects.equals(mPackageName, that.mPackageName);
}
@Override
public int hashCode() {
return Objects.hash(mStackTrace, mOp, mPackageName, mVersion);
}
/**
* The object is formatted as a JSON object and returned as a String
*
* @return JSON formatted string
*/
public String asJson() {
return "{"
+ "\"" + STACKTRACE + "\":\"" + mStackTrace.replace("\n", "\\n")
+ '\"' + ",\"" + OP + "\":" + mOp
+ ",\"" + PACKAGENAME + "\":\"" + mPackageName + '\"'
+ ",\"" + VERSION + "\":" + mVersion
+ '}';
}
}
/**
* Collects noteOps, noteProxyOps and startOps from AppOpsManager and writes it into a file
* which will be used for permissions data validation, the given parameters to this method
* will be logged in json format
*
* @param stackTrace stacktrace from the most recent call in AppOpsManager
* @param op op code
* @param packageName package making call
* @param version android version for this call
*/
@Override
public void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName,
long version) {
if (!AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
return;
}
Objects.requireNonNull(stackTrace);
Preconditions.checkArgument(op >= 0);
Preconditions.checkArgument(op < AppOpsManager._NUM_OP);
NoteOpTrace noteOpTrace = new NoteOpTrace(stackTrace, op, packageName, version);
boolean noteOpSetWasChanged;
synchronized (this) {
noteOpSetWasChanged = mNoteOpCallerStacktraces.add(noteOpTrace);
if (noteOpSetWasChanged && !mWriteNoteOpsScheduled) {
mWriteNoteOpsScheduled = true;
mHandler.postDelayed(PooledLambda.obtainRunnable((that) -> {
AsyncTask.execute(() -> {
that.writeNoteOps();
});
}, this), 2500);
}
}
}
@Immutable
private final class CheckOpsDelegateDispatcher {
private final @Nullable CheckOpsDelegate mPolicy;
private final @Nullable CheckOpsDelegate mCheckOpsDelegate;
CheckOpsDelegateDispatcher(@Nullable CheckOpsDelegate policy,
@Nullable CheckOpsDelegate checkOpsDelegate) {
mPolicy = policy;
mCheckOpsDelegate = checkOpsDelegate;
}
public @NonNull CheckOpsDelegate getCheckOpsDelegate() {
return mCheckOpsDelegate;
}
public int checkOperation(int code, int uid, String packageName,
@Nullable String attributionTag, boolean raw) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw,
this::checkDelegateOperationImpl);
} else {
return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw,
AppOpsService.this::checkOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
return checkDelegateOperationImpl(code, uid, packageName, attributionTag, raw);
}
return checkOperationImpl(code, uid, packageName, attributionTag, raw);
}
private int checkDelegateOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, boolean raw) {
return mCheckOpsDelegate.checkOperation(code, uid, packageName, attributionTag, raw,
AppOpsService.this::checkOperationImpl);
}
public int checkAudioOperation(int code, int usage, int uid, String packageName) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.checkAudioOperation(code, usage, uid, packageName,
this::checkDelegateAudioOperationImpl);
} else {
return mPolicy.checkAudioOperation(code, usage, uid, packageName,
AppOpsService.this::checkAudioOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
return checkDelegateAudioOperationImpl(code, usage, uid, packageName);
}
return checkAudioOperationImpl(code, usage, uid, packageName);
}
private int checkDelegateAudioOperationImpl(int code, int usage, int uid,
String packageName) {
return mCheckOpsDelegate.checkAudioOperation(code, usage, uid, packageName,
AppOpsService.this::checkAudioOperationImpl);
}
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.noteOperation(code, uid, packageName, attributionTag,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
this::noteDelegateOperationImpl);
} else {
return mPolicy.noteOperation(code, uid, packageName, attributionTag,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
AppOpsService.this::noteOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
return noteDelegateOperationImpl(code, uid, packageName,
attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
}
return noteOperationImpl(code, uid, packageName, attributionTag,
shouldCollectAsyncNotedOp, message, shouldCollectMessage);
}
private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid,
@Nullable String packageName, @Nullable String featureId,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
AppOpsService.this::noteOperationImpl);
}
public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, this::noteDelegateProxyOperationImpl);
} else {
return mPolicy.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, AppOpsService.this::noteProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
return noteDelegateProxyOperationImpl(code,
attributionSource, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation);
}
return noteProxyOperationImpl(code, attributionSource, shouldCollectAsyncNotedOp,
message, shouldCollectMessage,skipProxyOperation);
}
private SyncNotedAppOp noteDelegateProxyOperationImpl(int code,
@NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage,
boolean skipProxyOperation) {
return mCheckOpsDelegate.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
AppOpsService.this::noteProxyOperationImpl);
}
public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
@Nullable String packageName, @NonNull String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage,
@AttributionFlags int attributionFlags, int attributionChainId) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.startOperation(token, code, uid, packageName,
attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, attributionFlags, attributionChainId,
this::startDelegateOperationImpl);
} else {
return mPolicy.startOperation(token, code, uid, packageName, attributionTag,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, attributionFlags, attributionChainId,
AppOpsService.this::startOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
return startDelegateOperationImpl(token, code, uid, packageName, attributionTag,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, attributionFlags, attributionChainId);
}
return startOperationImpl(token, code, uid, packageName, attributionTag,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
attributionFlags, attributionChainId);
}
private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId) {
return mCheckOpsDelegate.startOperation(token, code, uid, packageName, attributionTag,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
}
public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId,
this::startDelegateProxyOperationImpl);
} else {
return mPolicy.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId,
AppOpsService.this::startProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
return startDelegateProxyOperationImpl(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
}
return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) {
return mCheckOpsDelegate.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs,
attributionChainId, AppOpsService.this::startProxyOperationImpl);
}
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag,
this::finishDelegateOperationImpl);
} else {
mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag,
AppOpsService.this::finishOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
finishDelegateOperationImpl(clientId, code, uid, packageName, attributionTag);
} else {
finishOperationImpl(clientId, code, uid, packageName, attributionTag);
}
}
private void finishDelegateOperationImpl(IBinder clientId, int code, int uid,
String packageName, String attributionTag) {
mCheckOpsDelegate.finishOperation(clientId, code, uid, packageName, attributionTag,
AppOpsService.this::finishOperationImpl);
}
public void finishProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
mPolicy.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, this::finishDelegateProxyOperationImpl);
} else {
mPolicy.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
finishDelegateProxyOperationImpl(clientId, code, attributionSource,
skipProxyOperation);
} else {
finishProxyOperationImpl(clientId, code, attributionSource, skipProxyOperation);
}
}
private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
return null;
}
}
}