summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/appop/AppOpsServiceImpl.java6523
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java625
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp2.java870
3 files changed, 8018 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
new file mode 100644
index 000000000000..58cecd00c83d
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -0,0 +1,6523 @@
+/*
+ * 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_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_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.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
+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.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.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.pkg.AndroidPackage;
+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 AppOpsServiceImpl extends IAppOpsService.Stub implements PersistenceScheduler {
+ 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<>();
+
+ private static final int NO_VERSION = -1;
+ /** Increment by one every time and add the corresponding upgrade logic in
+ * {@link #upgradeLocked(int)} below. The first version was 1 */
+ 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 mFile;
+ private final @Nullable File mNoteOpCallerStacktracesFile;
+ final Handler mHandler;
+
+ /**
+ * Pool for {@link AttributedOp2.OpEventProxyInfoPool} to avoid to constantly reallocate new
+ * objects
+ */
+ @GuardedBy("this")
+ final AttributedOp2.OpEventProxyInfoPool mOpEventProxyInfoPool =
+ new AttributedOp2.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+ /**
+ * Pool for {@link AttributedOp2.InProgressStartOpEventPool} to avoid to constantly reallocate
+ * new objects
+ */
+ @GuardedBy("this")
+ final AttributedOp2.InProgressStartOpEventPool mInProgressStartOpEventPool =
+ new AttributedOp2.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 (AppOpsServiceImpl.this) {
+ mWriteScheduled = false;
+ mFastWriteScheduled = false;
+ AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ @Override protected Void doInBackground(Void... params) {
+ writeState();
+ 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;
+
+ /** Interface for app-op modes.*/
+ @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
+
+ /** Interface for app-op restrictions.*/
+ @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
+
+ private AppOpsUidStateTracker mUidStateTracker;
+
+ /** Hands the definition of foreground and uid states */
+ @GuardedBy("this")
+ public AppOpsUidStateTracker getUidStateTracker() {
+ if (mUidStateTracker == null) {
+ mUidStateTracker = new AppOpsUidStateTrackerImpl2(
+ LocalServices.getService(ActivityManagerInternal.class),
+ mHandler,
+ r -> {
+ synchronized (AppOpsServiceImpl.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 (AppOpsServiceImpl.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;
+
+ public ArrayMap<String, Ops> pkgOps;
+
+ // 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() {
+ mAppOpsServiceInterface.removeUid(uid);
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+ }
+ pkgOps = null;
+ }
+
+ public boolean isDefault() {
+ boolean areAllPackageModesDefault = true;
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
+ UserHandle.getUserId(uid))) {
+ areAllPackageModesDefault = false;
+ break;
+ }
+ }
+ }
+ return (pkgOps == null || pkgOps.isEmpty())
+ && mAppOpsServiceInterface.areUidModesDefault(uid)
+ && areAllPackageModesDefault;
+ }
+
+ // Functions for uid mode access and manipulation.
+ public SparseIntArray getNonDefaultUidModes() {
+ return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
+ }
+
+ public int getUidMode(int op) {
+ return mAppOpsServiceInterface.getUidMode(uid, op);
+ }
+
+ public boolean setUidMode(int op, int mode) {
+ return mAppOpsServiceInterface.setUidMode(uid, op, mode);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ int evalMode(int op, int mode) {
+ return getUidStateTracker().evalMode(uid, op, mode);
+ }
+
+ public void evalForegroundOps() {
+ foregroundOps = null;
+ foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
+ if (pkgOps != null) {
+ for (int i = pkgOps.size() - 1; i >= 0; i--) {
+ foregroundOps = mAppOpsServiceInterface
+ .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)}. */
+ 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 -> AttributedOp2 */
+ final ArrayMap<String, AttributedOp2> 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 mAppOpsServiceInterface.getPackageMode(packageName, this.op,
+ UserHandle.getUserId(this.uid));
+ }
+ void setMode(@Mode int mode) {
+ mAppOpsServiceInterface.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 AttributedOp2 getOrCreateAttribution(@NonNull Op parent,
+ @Nullable String attributionTag) {
+ AttributedOp2 attributedOp;
+
+ attributedOp = mAttributions.get(attributionTag);
+ if (attributedOp == null) {
+ attributedOp = new AttributedOp2(AppOpsServiceImpl.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 AttributedOp2#onClientDeath attributedOp.onClientDeath(clientId)}.
+ */
+ static void onClientDeath(@NonNull AttributedOp2 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);
+ }
+ }
+
+ public AppOpsServiceImpl(File storagePath, 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));
+ }
+ mAppOpsServiceInterface =
+ new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+ mAppOpsServiceInterface);
+
+ LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+ mFile = new AtomicFile(storagePath, "appops");
+ if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
+ mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
+ "noteOpStackTraces.json");
+ readNoteOpCallerStackTraces();
+ } else {
+ mNoteOpCallerStacktracesFile = null;
+ }
+ mHandler = handler;
+ mConstants = new Constants(mHandler);
+ readState();
+ }
+
+ public void publish() {
+ ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
+ LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
+ }
+
+ /** Handler for work when packages are removed or 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(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+ synchronized (AppOpsServiceImpl.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
+ Ops removedOps = uidState.pkgOps.remove(pkgName);
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
+ } else 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 (AppOpsServiceImpl.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == 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);
+
+ AttributedOp2 newAttributedOp2 = op.getOrCreateAttribution(op,
+ newAttributionTag);
+ newAttributedOp2.add(op.mAttributions.valueAt(attributionNum));
+ op.mAttributions.removeAt(attributionNum);
+
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+ }
+ }
+ };
+
+ public void systemReady() {
+ mConstants.startMonitoring(mContext.getContentResolver());
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+ IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ 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;
+ if (pkgs == null) {
+ continue;
+ }
+
+ 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");
+ }
+ }
+ }
+
+ 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 (AppOpsServiceImpl.this) {
+ onModeChangedListeners =
+ mAppOpsServiceInterface.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);
+
+ final IntentFilter packageAddedFilter = new IntentFilter();
+ packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageAddedFilter.addDataScheme("package");
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Uri data = intent.getData();
+
+ final String packageName = data.getSchemeSpecificPart();
+ PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
+ if (isSamplingTarget(pi)) {
+ synchronized (AppOpsServiceImpl.this) {
+ mRarelyUsedPackages.add(packageName);
+ }
+ }
+ }
+ }, packageAddedFilter);
+
+ 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ public void packageRemoved(int uid, String packageName) {
+ synchronized (this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ return;
+ }
+
+ Ops removedOps = null;
+
+ // Remove any package state if such.
+ if (uidState.pkgOps != null) {
+ removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+
+ // If we just nuked the last package state check if the UID is valid.
+ if (removedOps != null && uidState.pkgOps.isEmpty()
+ && getPackagesForUid(uid).length <= 0) {
+ uidState.clear();
+ mUidStates.remove(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++) {
+ AttributedOp2 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 ForegroundPolicyInterface
+ 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(
+ AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
+ this, code, uidState.uid, true, null));
+ } else if (uidState.pkgOps != null) {
+ final ArraySet<OnOpModeChangedListener> listenerSet =
+ mAppOpsServiceInterface.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(
+ AppOpsServiceImpl::notifyOpChanged,
+ this, listenerSet.valueAt(cbi), code, uidState.uid,
+ uidState.pkgOps.keyAt(pkgi)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (uidState != null && uidState.pkgOps != 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++) {
+ AttributedOp2 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) {
+ writeState();
+ }
+ 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 == null || 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) {
+ final int callingUid = Binder.getCallingUid();
+ // We get to access everything
+ if (callingUid == Process.myPid()) {
+ return;
+ }
+ // Apps can access their own data
+ 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,
+ Binder.getCallingPid(), 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");
+ writeState();
+ 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);
+ mAppOpsServiceInterface.removePackage(ops.packageName,
+ UserHandle.getUserId(uidState.uid));
+ if (pkgOps.isEmpty()) {
+ uidState.pkgOps = null;
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(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);
+ }
+
+ 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;
+ mAppOpsServiceInterface.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);
+ }
+
+ private 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) {
+ 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 =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArraySet<>();
+ }
+ repCbs.addAll(cbs);
+ }
+ cbs = mAppOpsServiceInterface.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(
+ AppOpsServiceImpl::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) {
+ mAppOpsServiceInterface.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);
+ uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+ for (String packageName : getPackagesForUid(uidState.uid)) {
+ callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
+ previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(code));
+ callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
+ previousMode, mAppOpsServiceInterface
+ .getPackageModeChangedListeners(packageName));
+
+ allChanges = addChange(allChanges, code, uidState.uid,
+ packageName, previousMode);
+ }
+ }
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ 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)
+ && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+ int previousMode = curOp.getMode();
+ curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+ changed = true;
+ uidChanged = true;
+ final int uid = curOp.uidState.uid;
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode, mAppOpsServiceInterface
+ .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();
+ mAppOpsServiceInterface.removePackage(packageName,
+ UserHandle.getUserId(uidState.uid));
+ }
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(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(
+ AppOpsServiceImpl::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 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) {
+ mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
+ }
+ if (mayWatchPackageName) {
+ mAppOpsServiceInterface.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();
+ mAppOpsServiceInterface.removeListener(cb);
+ }
+
+ evalAllForegroundOpsLocked();
+ }
+ }
+
+ public CheckOpsDelegate getAppOpsServiceDelegate() {
+ synchronized (AppOpsServiceImpl.this) {
+ final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
+ return (dispatcher != null) ? dispatcher.getCheckOpsDelegate() : null;
+ }
+ }
+
+ public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
+ synchronized (AppOpsServiceImpl.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) {
+ Slog.e(TAG, "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(
+ AppOpsServiceImpl::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(
+ AppOpsServiceImpl::notifyWatchersOfChange, this,
+ AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyWatchersOfChange, this,
+ AppOpsManager.OP_VIBRATE, UID_ANY));
+ }
+
+ @Override
+ public int checkPackage(int uid, String packageName) {
+ Objects.requireNonNull(packageName);
+ try {
+ verifyAndGetBypass(uid, packageName, null);
+ // 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) {
+ Slog.e(TAG, "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 AttributedOp2 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 (AppOpsServiceImpl.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, 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.
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+ int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ 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) {
+ Slog.e(TAG, "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 AttributedOp2 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) {
+ 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 AttributedOp2 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(
+ AppOpsServiceImpl::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(
+ AppOpsServiceImpl::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(
+ AppOpsServiceImpl::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);
+ }
+
+ return mPackageManagerInternal;
+ }
+
+ /**
+ * 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 AndroidPackage pkg) {
+ return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+ mContext.checkPermission(android.Manifest.permission
+ .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * @see #verifyAndGetBypass(int, String, String, String)
+ */
+ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+ @Nullable String attributionTag) {
+ return verifyAndGetBypass(uid, packageName, attributionTag, null);
+ }
+
+ /**
+ * 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
+ *
+ * @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) {
+ 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 != null) {
+ 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)) {
+ 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);
+ AndroidPackage pkg = pmInt.getPackage(packageName);
+ if (pkg != null) {
+ isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+ pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+ bypass = getBypassforPackage(pkg);
+ }
+ 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) {
+ 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;
+ }
+
+ if (uidState.pkgOps == null) {
+ if (!edit) {
+ return null;
+ }
+ uidState.pkgOps = new ArrayMap<>();
+ }
+
+ 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;
+ }
+
+ @Override
+ public void scheduleWriteLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+ }
+ }
+
+ @Override
+ public 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;
+ }
+
+ void readState() {
+ int oldVersion = NO_VERSION;
+ synchronized (mFile) {
+ synchronized (this) {
+ FileInputStream stream;
+ try {
+ stream = mFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ mUidStates.clear();
+ mAppOpsServiceInterface.clearAllModes();
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+
+ 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")) {
+ readUidOps(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();
+ mAppOpsServiceInterface.clearAllModes();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ synchronized (this) {
+ upgradeLocked(oldVersion);
+ }
+ }
+
+ private void upgradeRunAnyInBackgroundLocked() {
+ for (int i = 0; i < mUidStates.size(); i++) {
+ final UidState uidState = mUidStates.valueAt(i);
+ if (uidState == null) {
+ continue;
+ }
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null) {
+ final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (idx >= 0) {
+ uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+ opModes.valueAt(idx));
+ }
+ }
+ if (uidState.pkgOps == null) {
+ continue;
+ }
+ boolean changed = false;
+ for (int j = 0; j < uidState.pkgOps.size(); j++) {
+ Ops ops = uidState.pkgOps.valueAt(j);
+ if (ops != null) {
+ final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+ final Op copy = new Op(op.uidState, op.packageName,
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+ copy.setMode(op.getMode());
+ ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ uidState.evalForegroundOps();
+ }
+ }
+ }
+
+ private void upgradeLocked(int oldVersion) {
+ if (oldVersion >= CURRENT_VERSION) {
+ return;
+ }
+ Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+ switch (oldVersion) {
+ case NO_VERSION:
+ upgradeRunAnyInBackgroundLocked();
+ // fall through
+ case 1:
+ // for future upgrades
+ }
+ scheduleFastWriteLocked();
+ }
+
+ private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ final int uid = parser.getAttributeInt(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("op")) {
+ final int code = parser.getAttributeInt(null, "n");
+ final int mode = parser.getAttributeInt(null, "m");
+ setUidMode(code, uid, mode);
+ } else {
+ Slog.w(TAG, "Unknown element under <uid-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ uidState.evalForegroundOps();
+ }
+
+ private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+ @Nullable String attribution)
+ throws NumberFormatException, IOException, XmlPullParserException {
+ final AttributedOp2 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);
+
+ final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+ op.setMode(mode);
+
+ 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);
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ uidState.pkgOps = new ArrayMap<>();
+ }
+ Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ ops = new Ops(pkgName, uidState);
+ uidState.pkgOps.put(pkgName, ops);
+ }
+ ops.put(op.op, op);
+ }
+
+ void writeState() {
+ synchronized (mFile) {
+ FileOutputStream stream;
+ try {
+ stream = mFile.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);
+
+ SparseArray<SparseIntArray> uidStatesClone;
+ synchronized (this) {
+ uidStatesClone = new SparseArray<>(mUidStates.size());
+
+ final int uidStateCount = mUidStates.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ UidState uidState = mUidStates.valueAt(uidStateNum);
+ int uid = mUidStates.keyAt(uidStateNum);
+
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null && opModes.size() > 0) {
+ uidStatesClone.put(uid, opModes);
+ }
+ }
+ }
+
+ final int uidStateCount = uidStatesClone.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+ if (opModes != null && opModes.size() > 0) {
+ out.startTag(null, "uid");
+ out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+ final int opCount = opModes.size();
+ for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+ final int op = opModes.keyAt(opCountNum);
+ final int mode = opModes.valueAt(opCountNum);
+ out.startTag(null, "op");
+ out.attributeInt(null, "n", op);
+ out.attributeInt(null, "m", mode);
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ }
+
+ 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();
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state, restoring backup.", e);
+ mFile.failWrite(stream);
+ }
+ }
+ mHistoricalRegistry.writeAndClearDiscreteHistory();
+ }
+
+ static class Shell extends ShellCommand {
+ final IAppOpsService mInterface;
+ final AppOpsServiceImpl 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, AppOpsServiceImpl 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.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.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 AttributedOp2 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++) {
+ AttributedOp2.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 |= mAppOpsServiceInterface.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 (AppOpsServiceImpl.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(
+ AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::updateStartedOpModeForUser, this, code, restricted,
+ userHandle));
+ }
+
+ if (restrictionState.isDefault()) {
+ mOpUserRestrictions.remove(token);
+ restrictionState.destroy();
+ }
+ }
+ }
+
+ private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+ synchronized (AppOpsServiceImpl.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 || uidState.pkgOps == 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++) {
+ AttributedOp2 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 = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (modeChangedListenerSet == null) {
+ return;
+ }
+ }
+
+ notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ }
+
+ @Override
+ public void removeUser(int userHandle) throws RemoteException {
+ checkSystemUid("removeUser");
+ synchronized (AppOpsServiceImpl.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 (AppOpsServiceImpl.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 (AppOpsServiceImpl.this) {
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+ UserHandle.getCallingUserId());
+ if (uid == Process.INVALID_UID) {
+ return;
+ }
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ Ops removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsServiceInterface.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(
+ AppOpsServiceImpl::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 (AppOpsServiceImpl.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 AppOpsManagerInternalImpl extends AppOpsManagerInternal {
+ @Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
+ synchronized (AppOpsServiceImpl.this) {
+ mProfileOwners = owners;
+ }
+ }
+
+ @Override
+ public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
+ boolean visible) {
+ AppOpsServiceImpl.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 (AppOpsServiceImpl.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(
+ AppOpsServiceImpl::notifyWatchersOfChange, AppOpsServiceImpl.this, code,
+ UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::updateStartedOpModeForUser, AppOpsServiceImpl.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 (AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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, AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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, AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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, AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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,
+ AppOpsServiceImpl.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, AppOpsServiceImpl.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, AppOpsServiceImpl.this::finishProxyOperationImpl);
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java
new file mode 100644
index 000000000000..96048f3b7ed0
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManager.ProcessCapability;
+import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
+import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
+import static android.app.AppOpsManager.UID_STATE_TOP;
+
+import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+
+class AppOpsUidStateTrackerImpl2 implements AppOpsUidStateTracker {
+
+ private static final String LOG_TAG = AppOpsUidStateTrackerImpl2.class.getSimpleName();
+
+ private final DelayableExecutor mExecutor;
+ private final Clock mClock;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private AppOpsServiceImpl.Constants mConstants;
+
+ private SparseIntArray mUidStates = new SparseIntArray();
+ private SparseIntArray mPendingUidStates = new SparseIntArray();
+ private SparseIntArray mCapability = new SparseIntArray();
+ private SparseIntArray mPendingCapability = new SparseIntArray();
+ private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray();
+ private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray();
+ private SparseLongArray mPendingCommitTime = new SparseLongArray();
+ private SparseBooleanArray mPendingGone = new SparseBooleanArray();
+
+ private ArrayMap<UidStateChangedCallback, Executor>
+ mUidStateChangedCallbacks = new ArrayMap<>();
+
+ private final EventLog mEventLog;
+
+ @VisibleForTesting
+ interface DelayableExecutor extends Executor {
+
+ void execute(Runnable runnable);
+
+ void executeDelayed(Runnable runnable, long delay);
+ }
+
+ AppOpsUidStateTrackerImpl2(ActivityManagerInternal activityManagerInternal,
+ Handler handler, Executor lockingExecutor, Clock clock,
+ AppOpsServiceImpl.Constants constants) {
+
+ this(activityManagerInternal, new DelayableExecutor() {
+ @Override
+ public void execute(Runnable runnable) {
+ handler.post(() -> lockingExecutor.execute(runnable));
+ }
+
+ @Override
+ public void executeDelayed(Runnable runnable, long delay) {
+ handler.postDelayed(() -> lockingExecutor.execute(runnable), delay);
+ }
+ }, clock, constants, handler.getLooper().getThread());
+ }
+
+ @VisibleForTesting
+ AppOpsUidStateTrackerImpl2(ActivityManagerInternal activityManagerInternal,
+ DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
+ Thread executorThread) {
+ mActivityManagerInternal = activityManagerInternal;
+ mExecutor = executor;
+ mClock = clock;
+ mConstants = constants;
+
+ mEventLog = new EventLog(executor, executorThread);
+ }
+
+ @Override
+ public int getUidState(int uid) {
+ return getUidStateLocked(uid);
+ }
+
+ private int getUidStateLocked(int uid) {
+ updateUidPendingStateIfNeeded(uid);
+ return mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+ }
+
+ @Override
+ public int evalMode(int uid, int code, int mode) {
+ if (mode != AppOpsManager.MODE_FOREGROUND) {
+ return mode;
+ }
+
+ int uidStateValue;
+ int capability;
+ boolean visibleAppWidget;
+ boolean pendingTop;
+ boolean tempAllowlist;
+ uidStateValue = getUidState(uid);
+ capability = getUidCapability(uid);
+ visibleAppWidget = getUidVisibleAppWidget(uid);
+ pendingTop = mActivityManagerInternal.isPendingTopUid(uid);
+ tempAllowlist = mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid);
+
+ int result = evalMode(uidStateValue, code, mode, capability, visibleAppWidget, pendingTop,
+ tempAllowlist);
+ mEventLog.logEvalForegroundMode(uid, uidStateValue, capability, code, result);
+ return result;
+ }
+
+ private static int evalMode(int uidState, int code, int mode, int capability,
+ boolean appWidgetVisible, boolean pendingTop, boolean tempAllowlist) {
+ if (mode != AppOpsManager.MODE_FOREGROUND) {
+ return mode;
+ }
+
+ if (appWidgetVisible || pendingTop || tempAllowlist) {
+ return MODE_ALLOWED;
+ }
+
+ switch (code) {
+ case AppOpsManager.OP_FINE_LOCATION:
+ case AppOpsManager.OP_COARSE_LOCATION:
+ case AppOpsManager.OP_MONITOR_LOCATION:
+ case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
+ if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) == 0) {
+ return MODE_IGNORED;
+ } else {
+ return MODE_ALLOWED;
+ }
+ case OP_CAMERA:
+ if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) == 0) {
+ return MODE_IGNORED;
+ } else {
+ return MODE_ALLOWED;
+ }
+ case OP_RECORD_AUDIO:
+ case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO:
+ if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) == 0) {
+ return MODE_IGNORED;
+ } else {
+ return MODE_ALLOWED;
+ }
+ }
+
+ if (uidState > AppOpsManager.resolveFirstUnrestrictedUidState(code)) {
+ return MODE_IGNORED;
+ }
+
+ return MODE_ALLOWED;
+ }
+
+ @Override
+ public void addUidStateChangedCallback(Executor executor, UidStateChangedCallback callback) {
+ if (mUidStateChangedCallbacks.containsKey(callback)) {
+ throw new IllegalStateException("Callback is already registered.");
+ }
+
+ mUidStateChangedCallbacks.put(callback, executor);
+ }
+
+ @Override
+ public void removeUidStateChangedCallback(UidStateChangedCallback callback) {
+ if (!mUidStateChangedCallbacks.containsKey(callback)) {
+ throw new IllegalStateException("Callback is not registered.");
+ }
+ mUidStateChangedCallbacks.remove(callback);
+ }
+
+ @Override
+ public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+ int numUids = uidPackageNames.size();
+ for (int i = 0; i < numUids; i++) {
+ int uid = uidPackageNames.keyAt(i);
+ mPendingVisibleAppWidget.put(uid, visible);
+
+ commitUidPendingState(uid);
+ }
+ }
+
+ @Override
+ public void updateUidProcState(int uid, int procState, int capability) {
+ mEventLog.logUpdateUidProcState(uid, procState, capability);
+
+ int uidState = processStateToUidState(procState);
+
+ int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE);
+ int prevCapability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
+ int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+ int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
+ long pendingStateCommitTime = mPendingCommitTime.get(uid, 0);
+ if ((pendingStateCommitTime == 0
+ && (uidState != prevUidState || capability != prevCapability))
+ || (pendingStateCommitTime != 0
+ && (uidState != pendingUidState || capability != pendingCapability))) {
+ mPendingUidStates.put(uid, uidState);
+ mPendingCapability.put(uid, capability);
+
+ if (procState == PROCESS_STATE_NONEXISTENT) {
+ mPendingGone.put(uid, true);
+ commitUidPendingState(uid);
+ } else if (uidState < prevUidState
+ || (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ && prevUidState > UID_STATE_MAX_LAST_NON_RESTRICTED)) {
+ // We are moving to a more important state, or the new state may be in the
+ // foreground and the old state is in the background, then always do it
+ // immediately.
+ commitUidPendingState(uid);
+ } else if (uidState == prevUidState && capability != prevCapability) {
+ // No change on process state, but process capability has changed.
+ commitUidPendingState(uid);
+ } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED) {
+ // We are moving to a less important state, but it doesn't cross the restriction
+ // threshold.
+ commitUidPendingState(uid);
+ } else if (pendingStateCommitTime == 0) {
+ // We are moving to a less important state for the first time,
+ // delay the application for a bit.
+ final long settleTime;
+ if (prevUidState <= UID_STATE_TOP) {
+ settleTime = mConstants.TOP_STATE_SETTLE_TIME;
+ } else if (prevUidState <= UID_STATE_FOREGROUND_SERVICE) {
+ settleTime = mConstants.FG_SERVICE_STATE_SETTLE_TIME;
+ } else {
+ settleTime = mConstants.BG_STATE_SETTLE_TIME;
+ }
+ final long commitTime = mClock.elapsedRealtime() + settleTime;
+ mPendingCommitTime.put(uid, commitTime);
+
+ mExecutor.executeDelayed(PooledLambda.obtainRunnable(
+ AppOpsUidStateTrackerImpl2::updateUidPendingStateIfNeeded, this,
+ uid), settleTime + 1);
+ }
+ }
+ }
+
+ @Override
+ public void dumpUidState(PrintWriter pw, int uid, long nowElapsed) {
+ int state = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+ // if no pendingState set to state to suppress output
+ int pendingState = mPendingUidStates.get(uid, state);
+ pw.print(" state=");
+ pw.println(AppOpsManager.getUidStateName(state));
+ if (state != pendingState) {
+ pw.print(" pendingState=");
+ pw.println(AppOpsManager.getUidStateName(pendingState));
+ }
+ int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
+ // if no pendingCapability set to capability to suppress output
+ int pendingCapability = mPendingCapability.get(uid, capability);
+ pw.print(" capability=");
+ ActivityManager.printCapabilitiesFull(pw, capability);
+ pw.println();
+ if (capability != pendingCapability) {
+ pw.print(" pendingCapability=");
+ ActivityManager.printCapabilitiesFull(pw, pendingCapability);
+ pw.println();
+ }
+ boolean appWidgetVisible = mVisibleAppWidget.get(uid, false);
+ // if no pendingAppWidgetVisible set to appWidgetVisible to suppress output
+ boolean pendingAppWidgetVisible = mPendingVisibleAppWidget.get(uid, appWidgetVisible);
+ pw.print(" appWidgetVisible=");
+ pw.println(appWidgetVisible);
+ if (appWidgetVisible != pendingAppWidgetVisible) {
+ pw.print(" pendingAppWidgetVisible=");
+ pw.println(pendingAppWidgetVisible);
+ }
+ long pendingStateCommitTime = mPendingCommitTime.get(uid, 0);
+ if (pendingStateCommitTime != 0) {
+ pw.print(" pendingStateCommitTime=");
+ TimeUtils.formatDuration(pendingStateCommitTime, nowElapsed, pw);
+ pw.println();
+ }
+ }
+
+ @Override
+ public void dumpEvents(PrintWriter pw) {
+ mEventLog.dumpEvents(pw);
+ }
+
+ private void updateUidPendingStateIfNeeded(int uid) {
+ updateUidPendingStateIfNeededLocked(uid);
+ }
+
+ private void updateUidPendingStateIfNeededLocked(int uid) {
+ long pendingCommitTime = mPendingCommitTime.get(uid, 0);
+ if (pendingCommitTime != 0) {
+ long currentTime = mClock.elapsedRealtime();
+ if (currentTime < mPendingCommitTime.get(uid)) {
+ return;
+ }
+ commitUidPendingState(uid);
+ }
+ }
+
+ private void commitUidPendingState(int uid) {
+ int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+ int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
+ boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid, false);
+
+ int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+ int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
+ boolean visibleAppWidget = mVisibleAppWidget.get(uid, false);
+
+ if (uidState != pendingUidState
+ || capability != pendingCapability
+ || visibleAppWidget != pendingVisibleAppWidget) {
+ boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ || capability != pendingCapability
+ || visibleAppWidget != pendingVisibleAppWidget;
+
+ if (foregroundChange) {
+ // To save on memory usage, log only interesting changes.
+ mEventLog.logCommitUidState(uid, pendingUidState, pendingCapability,
+ pendingVisibleAppWidget);
+ }
+
+ for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
+ UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
+ Executor executor = mUidStateChangedCallbacks.valueAt(i);
+
+ executor.execute(PooledLambda.obtainRunnable(
+ UidStateChangedCallback::onUidStateChanged, cb, uid, pendingUidState,
+ foregroundChange));
+ }
+ }
+
+ if (mPendingGone.get(uid, false)) {
+ mUidStates.delete(uid);
+ mCapability.delete(uid);
+ mVisibleAppWidget.delete(uid);
+ mPendingGone.delete(uid);
+ } else {
+ mUidStates.put(uid, pendingUidState);
+ mCapability.put(uid, pendingCapability);
+ mVisibleAppWidget.put(uid, pendingVisibleAppWidget);
+ }
+
+ mPendingUidStates.delete(uid);
+ mPendingCapability.delete(uid);
+ mPendingVisibleAppWidget.delete(uid);
+ mPendingCommitTime.delete(uid);
+ }
+
+ private @ProcessCapability int getUidCapability(int uid) {
+ return mCapability.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE);
+ }
+
+ private boolean getUidVisibleAppWidget(int uid) {
+ return mVisibleAppWidget.get(uid, false);
+ }
+
+ private static class EventLog {
+
+ // These seems a bit too verbose and not as useful, turning off for now.
+ // DCE should be able to remove most associated code.
+ // Memory usage: 16 * size bytes
+ private static final int UPDATE_UID_PROC_STATE_LOG_MAX_SIZE = 0;
+ // Memory usage: 20 * size bytes
+ private static final int COMMIT_UID_STATE_LOG_MAX_SIZE = 200;
+ // Memory usage: 24 * size bytes
+ private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
+
+ private final DelayableExecutor mExecutor;
+ private final Thread mExecutorThread;
+
+ private int[][] mUpdateUidProcStateLog = new int[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE][3];
+ private long[] mUpdateUidProcStateLogTimestamps =
+ new long[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE];
+ private int mUpdateUidProcStateLogSize = 0;
+ private int mUpdateUidProcStateLogHead = 0;
+
+ private int[][] mCommitUidStateLog = new int[COMMIT_UID_STATE_LOG_MAX_SIZE][4];
+ private long[] mCommitUidStateLogTimestamps = new long[COMMIT_UID_STATE_LOG_MAX_SIZE];
+ private int mCommitUidStateLogSize = 0;
+ private int mCommitUidStateLogHead = 0;
+
+ private int[][] mEvalForegroundModeLog = new int[EVAL_FOREGROUND_MODE_MAX_SIZE][5];
+ private long[] mEvalForegroundModeLogTimestamps = new long[EVAL_FOREGROUND_MODE_MAX_SIZE];
+ private int mEvalForegroundModeLogSize = 0;
+ private int mEvalForegroundModeLogHead = 0;
+
+ EventLog(DelayableExecutor executor, Thread executorThread) {
+ mExecutor = executor;
+ mExecutorThread = executorThread;
+ }
+
+ void logUpdateUidProcState(int uid, int procState, int capability) {
+ if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE == 0) {
+ return;
+ }
+ mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logUpdateUidProcStateAsync,
+ this, System.currentTimeMillis(), uid, procState, capability));
+ }
+
+ void logUpdateUidProcStateAsync(long timestamp, int uid, int procState, int capability) {
+ int idx = (mUpdateUidProcStateLogHead + mUpdateUidProcStateLogSize)
+ % UPDATE_UID_PROC_STATE_LOG_MAX_SIZE;
+ if (mUpdateUidProcStateLogSize == UPDATE_UID_PROC_STATE_LOG_MAX_SIZE) {
+ mUpdateUidProcStateLogHead =
+ (mUpdateUidProcStateLogHead + 1) % UPDATE_UID_PROC_STATE_LOG_MAX_SIZE;
+ } else {
+ mUpdateUidProcStateLogSize++;
+ }
+
+ mUpdateUidProcStateLog[idx][0] = uid;
+ mUpdateUidProcStateLog[idx][1] = procState;
+ mUpdateUidProcStateLog[idx][2] = capability;
+ mUpdateUidProcStateLogTimestamps[idx] = timestamp;
+ }
+
+ void logCommitUidState(int uid, int uidState, int capability, boolean visible) {
+ if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
+ return;
+ }
+ mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
+ this, System.currentTimeMillis(), uid, uidState, capability, visible));
+ }
+
+ void logCommitUidStateAsync(long timestamp, int uid, int uidState, int capability,
+ boolean visible) {
+ int idx = (mCommitUidStateLogHead + mCommitUidStateLogSize)
+ % COMMIT_UID_STATE_LOG_MAX_SIZE;
+ if (mCommitUidStateLogSize == COMMIT_UID_STATE_LOG_MAX_SIZE) {
+ mCommitUidStateLogHead =
+ (mCommitUidStateLogHead + 1) % COMMIT_UID_STATE_LOG_MAX_SIZE;
+ } else {
+ mCommitUidStateLogSize++;
+ }
+
+ mCommitUidStateLog[idx][0] = uid;
+ mCommitUidStateLog[idx][1] = uidState;
+ mCommitUidStateLog[idx][2] = capability;
+ mCommitUidStateLog[idx][3] = visible ? 1 : 0;
+ mCommitUidStateLogTimestamps[idx] = timestamp;
+ }
+
+ void logEvalForegroundMode(int uid, int uidState, int capability, int code, int result) {
+ if (EVAL_FOREGROUND_MODE_MAX_SIZE == 0) {
+ return;
+ }
+ mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logEvalForegroundModeAsync,
+ this, System.currentTimeMillis(), uid, uidState, capability, code, result));
+ }
+
+ void logEvalForegroundModeAsync(long timestamp, int uid, int uidState, int capability,
+ int code, int result) {
+ int idx = (mEvalForegroundModeLogHead + mEvalForegroundModeLogSize)
+ % EVAL_FOREGROUND_MODE_MAX_SIZE;
+ if (mEvalForegroundModeLogSize == EVAL_FOREGROUND_MODE_MAX_SIZE) {
+ mEvalForegroundModeLogHead =
+ (mEvalForegroundModeLogHead + 1) % EVAL_FOREGROUND_MODE_MAX_SIZE;
+ } else {
+ mEvalForegroundModeLogSize++;
+ }
+
+ mEvalForegroundModeLog[idx][0] = uid;
+ mEvalForegroundModeLog[idx][1] = uidState;
+ mEvalForegroundModeLog[idx][2] = capability;
+ mEvalForegroundModeLog[idx][3] = code;
+ mEvalForegroundModeLog[idx][4] = result;
+ mEvalForegroundModeLogTimestamps[idx] = timestamp;
+ }
+
+ void dumpEvents(PrintWriter pw) {
+ int updateIdx = 0;
+ int commitIdx = 0;
+ int evalIdx = 0;
+
+ while (updateIdx < mUpdateUidProcStateLogSize
+ || commitIdx < mCommitUidStateLogSize
+ || evalIdx < mEvalForegroundModeLogSize) {
+ int updatePtr = 0;
+ int commitPtr = 0;
+ int evalPtr = 0;
+ if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE != 0) {
+ updatePtr = (mUpdateUidProcStateLogHead + updateIdx)
+ % UPDATE_UID_PROC_STATE_LOG_MAX_SIZE;
+ }
+ if (COMMIT_UID_STATE_LOG_MAX_SIZE != 0) {
+ commitPtr = (mCommitUidStateLogHead + commitIdx)
+ % COMMIT_UID_STATE_LOG_MAX_SIZE;
+ }
+ if (EVAL_FOREGROUND_MODE_MAX_SIZE != 0) {
+ evalPtr = (mEvalForegroundModeLogHead + evalIdx)
+ % EVAL_FOREGROUND_MODE_MAX_SIZE;
+ }
+
+ long aTimestamp = updateIdx < mUpdateUidProcStateLogSize
+ ? mUpdateUidProcStateLogTimestamps[updatePtr] : Long.MAX_VALUE;
+ long bTimestamp = commitIdx < mCommitUidStateLogSize
+ ? mCommitUidStateLogTimestamps[commitPtr] : Long.MAX_VALUE;
+ long cTimestamp = evalIdx < mEvalForegroundModeLogSize
+ ? mEvalForegroundModeLogTimestamps[evalPtr] : Long.MAX_VALUE;
+
+ if (aTimestamp <= bTimestamp && aTimestamp <= cTimestamp) {
+ dumpUpdateUidProcState(pw, updatePtr);
+ updateIdx++;
+ } else if (bTimestamp <= cTimestamp) {
+ dumpCommitUidState(pw, commitPtr);
+ commitIdx++;
+ } else {
+ dumpEvalForegroundMode(pw, evalPtr);
+ evalIdx++;
+ }
+ }
+ }
+
+ void dumpUpdateUidProcState(PrintWriter pw, int idx) {
+ long timestamp = mUpdateUidProcStateLogTimestamps[idx];
+ int uid = mUpdateUidProcStateLog[idx][0];
+ int procState = mUpdateUidProcStateLog[idx][1];
+ int capability = mUpdateUidProcStateLog[idx][2];
+
+ TimeUtils.dumpTime(pw, timestamp);
+
+ pw.print(" UPDATE_UID_PROC_STATE");
+
+ pw.print(" uid=");
+ pw.print(String.format("%-8d", uid));
+
+ pw.print(" procState=");
+ pw.print(String.format("%-30s", ActivityManager.procStateToString(procState)));
+
+ pw.print(" capability=");
+ pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
+
+ pw.println();
+ }
+
+ void dumpCommitUidState(PrintWriter pw, int idx) {
+ long timestamp = mCommitUidStateLogTimestamps[idx];
+ int uid = mCommitUidStateLog[idx][0];
+ int uidState = mCommitUidStateLog[idx][1];
+ int capability = mCommitUidStateLog[idx][2];
+ boolean visibleAppWidget = mCommitUidStateLog[idx][3] != 0;
+
+ TimeUtils.dumpTime(pw, timestamp);
+
+ pw.print(" COMMIT_UID_STATE ");
+
+ pw.print(" uid=");
+ pw.print(String.format("%-8d", uid));
+
+ pw.print(" uidState=");
+ pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
+
+ pw.print(" capability=");
+ pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
+
+ pw.print(" visibleAppWidget=");
+ pw.print(visibleAppWidget);
+
+ pw.println();
+ }
+
+ void dumpEvalForegroundMode(PrintWriter pw, int idx) {
+ long timestamp = mEvalForegroundModeLogTimestamps[idx];
+ int uid = mEvalForegroundModeLog[idx][0];
+ int uidState = mEvalForegroundModeLog[idx][1];
+ int capability = mEvalForegroundModeLog[idx][2];
+ int code = mEvalForegroundModeLog[idx][3];
+ int result = mEvalForegroundModeLog[idx][4];
+
+ TimeUtils.dumpTime(pw, timestamp);
+
+ pw.print(" EVAL_FOREGROUND_MODE ");
+
+ pw.print(" uid=");
+ pw.print(String.format("%-8d", uid));
+
+ pw.print(" uidState=");
+ pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
+
+ pw.print(" capability=");
+ pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
+
+ pw.print(" code=");
+ pw.print(String.format("%-20s", AppOpsManager.opToName(code)));
+
+ pw.print(" result=");
+ pw.print(AppOpsManager.modeToName(result));
+
+ pw.println();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AttributedOp2.java b/services/core/java/com/android/server/appop/AttributedOp2.java
new file mode 100644
index 000000000000..16fa7e185f94
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AttributedOp2.java
@@ -0,0 +1,870 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
+import static android.app.AppOpsManager.makeKey;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.Pools;
+import android.util.Slog;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+final class AttributedOp2 {
+ private final @NonNull AppOpsServiceImpl mAppOpsServiceImpl;
+ public final @Nullable String tag;
+ public final @NonNull AppOpsServiceImpl.Op parent;
+
+ /**
+ * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
+ *
+ * <p>Key is {@link AppOpsManager#makeKey}
+ */
+ // TODO(b/248108338)
+ // @GuardedBy("mAppOpsCheckingServiceImpl")
+ private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents;
+
+ /**
+ * Last rejected accesses for each uidState/opFlag combination
+ *
+ * <p>Key is {@link AppOpsManager#makeKey}
+ */
+ // TODO(b/248108338)
+ // @GuardedBy("mAppOpsCheckingServiceImpl")
+ private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents;
+
+ /**
+ * Currently in progress startOp events
+ *
+ * <p>Key is clientId
+ */
+ // TODO(b/248108338)
+ // @GuardedBy("mAppOpsCheckingServiceImpl")
+ @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
+
+ /**
+ * Currently paused startOp events
+ *
+ * <p>Key is clientId
+ */
+ // TODO(b/248108338)
+ // @GuardedBy("mAppOpsCheckingServiceImpl")
+ @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
+
+ AttributedOp2(@NonNull AppOpsServiceImpl AppOpsServiceImpl, @Nullable String tag,
+ @NonNull AppOpsServiceImpl.Op parent) {
+ mAppOpsServiceImpl = AppOpsServiceImpl;
+ this.tag = tag;
+ this.parent = parent;
+ }
+
+ /**
+ * Update state when noteOp was rejected or startOp->finishOp event finished
+ *
+ * @param proxyUid The uid of the proxy
+ * @param proxyPackageName The package name of the proxy
+ * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param uidState UID state of the app noteOp/startOp was called for
+ * @param flags OpFlags of the call
+ */
+ public void accessed(int proxyUid, @Nullable String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags) {
+ long accessTime = System.currentTimeMillis();
+ accessed(accessTime, -1, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState, flags);
+
+ mAppOpsServiceImpl.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+ parent.packageName, tag, uidState, flags, accessTime,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ }
+
+ /**
+ * Add an access that was previously collected.
+ *
+ * @param noteTime The time of the event
+ * @param duration The duration of the event
+ * @param proxyUid The uid of the proxy
+ * @param proxyPackageName The package name of the proxy
+ * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param uidState UID state of the app noteOp/startOp was called for
+ * @param flags OpFlags of the call
+ */
+ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsCheckingServiceImpl
+ public void accessed(long noteTime, long duration, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+ long key = makeKey(uidState, flags);
+
+ if (mAccessEvents == null) {
+ mAccessEvents = new LongSparseArray<>(1);
+ }
+
+ AppOpsManager.OpEventProxyInfo proxyInfo = null;
+ if (proxyUid != Process.INVALID_UID) {
+ proxyInfo = mAppOpsServiceImpl.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
+ proxyAttributionTag);
+ }
+
+ AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
+ if (existingEvent != null) {
+ existingEvent.reinit(noteTime, duration, proxyInfo,
+ mAppOpsServiceImpl.mOpEventProxyInfoPool);
+ } else {
+ mAccessEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, duration, proxyInfo));
+ }
+ }
+
+ /**
+ * Update state when noteOp/startOp was rejected.
+ *
+ * @param uidState UID state of the app noteOp is called for
+ * @param flags OpFlags of the call
+ */
+ public void rejected(@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+ rejected(System.currentTimeMillis(), uidState, flags);
+
+ mAppOpsServiceImpl.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid,
+ parent.packageName, tag, uidState, flags);
+ }
+
+ /**
+ * Add an rejection that was previously collected
+ *
+ * @param noteTime The time of the event
+ * @param uidState UID state of the app noteOp/startOp was called for
+ * @param flags OpFlags of the call
+ */
+ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsCheckingServiceImpl
+ public void rejected(long noteTime, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags) {
+ long key = makeKey(uidState, flags);
+
+ if (mRejectEvents == null) {
+ mRejectEvents = new LongSparseArray<>(1);
+ }
+
+ // We do not collect proxy information for rejections yet
+ AppOpsManager.NoteOpEvent existingEvent = mRejectEvents.get(key);
+ if (existingEvent != null) {
+ existingEvent.reinit(noteTime, -1, null, mAppOpsServiceImpl.mOpEventProxyInfoPool);
+ } else {
+ mRejectEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, -1, null));
+ }
+ }
+
+ /**
+ * Update state when start was called
+ *
+ * @param clientId Id of the startOp caller
+ * @param proxyUid The UID of the proxy app
+ * @param proxyPackageName The package name of the proxy app
+ * @param proxyAttributionTag The attribution tag of the proxy app
+ * @param uidState UID state of the app startOp is called for
+ * @param flags The proxy flags
+ * @param attributionFlags The attribution flags associated with this operation.
+ * @param attributionChainId The if of the attribution chain this operations is a part of.
+ */
+ public void started(@NonNull IBinder clientId, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
+ started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+ uidState, flags, /*triggerCallbackIfNeeded*/ true, attributionFlags,
+ attributionChainId);
+ }
+
+ private void started(@NonNull IBinder clientId, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ boolean triggerCallbackIfNeeded, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId) throws RemoteException {
+ startedOrPaused(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
+ /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
+ }
+
+ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsCheckingServiceImpl
+ private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ boolean triggerCallbackIfNeeded, boolean isStarted, @AppOpsManager.AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
+ if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
+ mAppOpsServiceImpl.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+ parent.packageName, tag, true, attributionFlags, attributionChainId);
+ }
+
+ if (isStarted && mInProgressEvents == null) {
+ mInProgressEvents = new ArrayMap<>(1);
+ } else if (!isStarted && mPausedInProgressEvents == null) {
+ mPausedInProgressEvents = new ArrayMap<>(1);
+ }
+ ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted
+ ? mInProgressEvents : mPausedInProgressEvents;
+
+ long startTime = System.currentTimeMillis();
+ InProgressStartOpEvent event = events.get(clientId);
+ if (event == null) {
+ event = mAppOpsServiceImpl.mInProgressStartOpEventPool.acquire(startTime,
+ SystemClock.elapsedRealtime(), clientId, tag,
+ PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
+ proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+ attributionFlags, attributionChainId);
+ events.put(clientId, event);
+ } else {
+ if (uidState != event.getUidState()) {
+ onUidStateChanged(uidState);
+ }
+ }
+
+ event.mNumUnfinishedStarts++;
+
+ if (isStarted) {
+ mAppOpsServiceImpl.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+ parent.packageName, tag, uidState, flags, startTime, attributionFlags,
+ attributionChainId);
+ }
+ }
+
+ /**
+ * Update state when finishOp was called. Will finish started ops, and delete paused ops.
+ *
+ * @param clientId Id of the finishOp caller
+ */
+ public void finished(@NonNull IBinder clientId) {
+ finished(clientId, true);
+ }
+
+ private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
+ finishOrPause(clientId, triggerCallbackIfNeeded, false);
+ }
+
+ /**
+ * Update state when paused or finished is called. If pausing, it records the op as
+ * stopping in the HistoricalRegistry, but does not delete it.
+ */
+ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsCheckingServiceImpl
+ private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
+ boolean isPausing) {
+ int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1;
+ if (indexOfToken < 0) {
+ finishPossiblyPaused(clientId, isPausing);
+ return;
+ }
+
+ InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
+ if (!isPausing) {
+ event.mNumUnfinishedStarts--;
+ }
+ // If we are pausing, create a NoteOpEvent, but don't change the InProgress event
+ if (event.mNumUnfinishedStarts == 0 || isPausing) {
+ if (!isPausing) {
+ event.finish();
+ mInProgressEvents.removeAt(indexOfToken);
+ }
+
+ if (mAccessEvents == null) {
+ mAccessEvents = new LongSparseArray<>(1);
+ }
+
+ AppOpsManager.OpEventProxyInfo proxyCopy = event.getProxy() != null
+ ? new AppOpsManager.OpEventProxyInfo(event.getProxy()) : null;
+
+ long accessDurationMillis =
+ SystemClock.elapsedRealtime() - event.getStartElapsedTime();
+ AppOpsManager.NoteOpEvent finishedEvent = new AppOpsManager.NoteOpEvent(
+ event.getStartTime(),
+ accessDurationMillis, proxyCopy);
+ mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
+ finishedEvent);
+
+ mAppOpsServiceImpl.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
+ parent.packageName, tag, event.getUidState(),
+ event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
+ event.getAttributionFlags(), event.getAttributionChainId());
+
+ if (!isPausing) {
+ mAppOpsServiceImpl.mInProgressStartOpEventPool.release(event);
+ if (mInProgressEvents.isEmpty()) {
+ mInProgressEvents = null;
+
+ // TODO ntmyren: Also callback for single attribution tag activity changes
+ if (triggerCallbackIfNeeded && !parent.isRunning()) {
+ mAppOpsServiceImpl.scheduleOpActiveChangedIfNeededLocked(parent.op,
+ parent.uid, parent.packageName, tag, false,
+ event.getAttributionFlags(), event.getAttributionChainId());
+ }
+ }
+ }
+ }
+ }
+
+ // Finish or pause (no-op) an already paused op
+ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsCheckingServiceImpl
+ private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
+ if (!isPaused()) {
+ Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
+ return;
+ }
+
+ int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
+ if (indexOfToken < 0) {
+ Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
+ return;
+ } else if (isPausing) {
+ // already paused
+ return;
+ }
+
+ // no need to record a paused event finishing.
+ InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken);
+ event.mNumUnfinishedStarts--;
+ if (event.mNumUnfinishedStarts == 0) {
+ mPausedInProgressEvents.removeAt(indexOfToken);
+ mAppOpsServiceImpl.mInProgressStartOpEventPool.release(event);
+ if (mPausedInProgressEvents.isEmpty()) {
+ mPausedInProgressEvents = null;
+ }
+ }
+ }
+
+ /**
+ * Create an event that will be started, if the op is unpaused.
+ */
+ public void createPaused(@NonNull IBinder clientId, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
+ startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+ uidState, flags, true, false, attributionFlags, attributionChainId);
+ }
+
+ /**
+ * Pause all currently started ops. This will create a HistoricalRegistry
+ */
+ public void pause() {
+ if (!isRunning()) {
+ return;
+ }
+
+ if (mPausedInProgressEvents == null) {
+ mPausedInProgressEvents = new ArrayMap<>(1);
+ }
+
+ for (int i = 0; i < mInProgressEvents.size(); i++) {
+ InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
+ mPausedInProgressEvents.put(event.getClientId(), event);
+ finishOrPause(event.getClientId(), true, true);
+
+ mAppOpsServiceImpl.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+ parent.packageName, tag, false,
+ event.getAttributionFlags(), event.getAttributionChainId());
+ }
+ mInProgressEvents = null;
+ }
+
+ /**
+ * Unpause all currently paused ops. This will reinitialize their start and duration
+ * times, but keep all other values the same
+ */
+ public void resume() {
+ if (!isPaused()) {
+ return;
+ }
+
+ if (mInProgressEvents == null) {
+ mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size());
+ }
+ boolean shouldSendActive = !mPausedInProgressEvents.isEmpty()
+ && mInProgressEvents.isEmpty();
+
+ long startTime = System.currentTimeMillis();
+ for (int i = 0; i < mPausedInProgressEvents.size(); i++) {
+ InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i);
+ mInProgressEvents.put(event.getClientId(), event);
+ event.setStartElapsedTime(SystemClock.elapsedRealtime());
+ event.setStartTime(startTime);
+ mAppOpsServiceImpl.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+ parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
+ event.getAttributionFlags(), event.getAttributionChainId());
+ if (shouldSendActive) {
+ mAppOpsServiceImpl.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+ parent.packageName, tag, true, event.getAttributionFlags(),
+ event.getAttributionChainId());
+ }
+ // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
+ // TODO ntmyren: figure out how to get the real mode.
+ mAppOpsServiceImpl.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
+ parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
+ event.getAttributionFlags(), event.getAttributionChainId());
+ }
+ mPausedInProgressEvents = null;
+ }
+
+ /**
+ * Called in the case the client dies without calling finish first
+ *
+ * @param clientId The client that died
+ */
+ void onClientDeath(@NonNull IBinder clientId) {
+ synchronized (mAppOpsServiceImpl) {
+ if (!isPaused() && !isRunning()) {
+ return;
+ }
+
+ ArrayMap<IBinder, InProgressStartOpEvent> events = isPaused()
+ ? mPausedInProgressEvents : mInProgressEvents;
+ InProgressStartOpEvent deadEvent = events.get(clientId);
+ if (deadEvent != null) {
+ deadEvent.mNumUnfinishedStarts = 1;
+ }
+
+ finished(clientId);
+ }
+ }
+
+ /**
+ * Notify that the state of the uid changed
+ *
+ * @param newState The new state
+ */
+ public void onUidStateChanged(@AppOpsManager.UidState int newState) {
+ if (!isPaused() && !isRunning()) {
+ return;
+ }
+
+ boolean isRunning = isRunning();
+ ArrayMap<IBinder, InProgressStartOpEvent> events =
+ isRunning ? mInProgressEvents : mPausedInProgressEvents;
+
+ int numInProgressEvents = events.size();
+ List<IBinder> binders = new ArrayList<>(events.keySet());
+ for (int i = 0; i < numInProgressEvents; i++) {
+ InProgressStartOpEvent event = events.get(binders.get(i));
+
+ if (event != null && event.getUidState() != newState) {
+ try {
+ // Remove all but one unfinished start count and then call finished() to
+ // remove start event object
+ int numPreviousUnfinishedStarts = event.mNumUnfinishedStarts;
+ event.mNumUnfinishedStarts = 1;
+ AppOpsManager.OpEventProxyInfo proxy = event.getProxy();
+
+ finished(event.getClientId(), false);
+
+ // Call started() to add a new start event object and then add the
+ // previously removed unfinished start counts back
+ if (proxy != null) {
+ startedOrPaused(event.getClientId(), proxy.getUid(),
+ proxy.getPackageName(), proxy.getAttributionTag(), newState,
+ event.getFlags(), false, isRunning,
+ event.getAttributionFlags(), event.getAttributionChainId());
+ } else {
+ startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
+ newState, event.getFlags(), false, isRunning,
+ event.getAttributionFlags(), event.getAttributionChainId());
+ }
+
+ events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
+ InProgressStartOpEvent newEvent = events.get(binders.get(i));
+ if (newEvent != null) {
+ newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
+ }
+ } catch (RemoteException e) {
+ if (AppOpsServiceImpl.DEBUG) {
+ Slog.e(AppOpsServiceImpl.TAG,
+ "Cannot switch to new uidState " + newState);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Combine {@code a} and {@code b} and return the result. The result might be {@code a}
+ * or {@code b}. If there is an event for the same key in both the later event is retained.
+ */
+ private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> add(
+ @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> a,
+ @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> b) {
+ if (a == null) {
+ return b;
+ }
+
+ if (b == null) {
+ return a;
+ }
+
+ int numEventsToAdd = b.size();
+ for (int i = 0; i < numEventsToAdd; i++) {
+ long keyOfEventToAdd = b.keyAt(i);
+ AppOpsManager.NoteOpEvent bEvent = b.valueAt(i);
+ AppOpsManager.NoteOpEvent aEvent = a.get(keyOfEventToAdd);
+
+ if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) {
+ a.put(keyOfEventToAdd, bEvent);
+ }
+ }
+
+ return a;
+ }
+
+ /**
+ * Add all data from the {@code opToAdd} to this op.
+ *
+ * <p>If there is an event for the same key in both the later event is retained.
+ * <p>{@code opToAdd} should not be used after this method is called.
+ *
+ * @param opToAdd The op to add
+ */
+ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsCheckingServiceImpl
+ public void add(@NonNull AttributedOp2 opToAdd) {
+ if (opToAdd.isRunning() || opToAdd.isPaused()) {
+ ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
+ opToAdd.isRunning()
+ ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
+ Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
+ + opToAdd.isRunning());
+
+ int numInProgressEvents = ignoredEvents.size();
+ for (int i = 0; i < numInProgressEvents; i++) {
+ InProgressStartOpEvent event = ignoredEvents.valueAt(i);
+
+ event.finish();
+ mAppOpsServiceImpl.mInProgressStartOpEventPool.release(event);
+ }
+ }
+
+ mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents);
+ mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents);
+ }
+
+ public boolean isRunning() {
+ return mInProgressEvents != null && !mInProgressEvents.isEmpty();
+ }
+
+ public boolean isPaused() {
+ return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
+ }
+
+ boolean hasAnyTime() {
+ return (mAccessEvents != null && mAccessEvents.size() > 0)
+ || (mRejectEvents != null && mRejectEvents.size() > 0);
+ }
+
+ /**
+ * Clone a {@link LongSparseArray} and clone all values.
+ */
+ private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> deepClone(
+ @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> original) {
+ if (original == null) {
+ return original;
+ }
+
+ int size = original.size();
+ LongSparseArray<AppOpsManager.NoteOpEvent> clone = new LongSparseArray<>(size);
+ for (int i = 0; i < size; i++) {
+ clone.put(original.keyAt(i), new AppOpsManager.NoteOpEvent(original.valueAt(i)));
+ }
+
+ return clone;
+ }
+
+ @NonNull AppOpsManager.AttributedOpEntry createAttributedOpEntryLocked() {
+ LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = deepClone(mAccessEvents);
+
+ // Add in progress events as access events
+ if (isRunning()) {
+ long now = SystemClock.elapsedRealtime();
+ int numInProgressEvents = mInProgressEvents.size();
+
+ if (accessEvents == null) {
+ accessEvents = new LongSparseArray<>(numInProgressEvents);
+ }
+
+ for (int i = 0; i < numInProgressEvents; i++) {
+ InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
+
+ accessEvents.append(makeKey(event.getUidState(), event.getFlags()),
+ new AppOpsManager.NoteOpEvent(event.getStartTime(),
+ now - event.getStartElapsedTime(),
+ event.getProxy()));
+ }
+ }
+
+ LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = deepClone(mRejectEvents);
+
+ return new AppOpsManager.AttributedOpEntry(parent.op, isRunning(), accessEvents,
+ rejectEvents);
+ }
+
+ /** A in progress startOp->finishOp event */
+ static final class InProgressStartOpEvent implements IBinder.DeathRecipient {
+ /** Wall clock time of startOp event (not monotonic) */
+ private long mStartTime;
+
+ /** Elapsed time since boot of startOp event */
+ private long mStartElapsedTime;
+
+ /** Id of the client that started the event */
+ private @NonNull IBinder mClientId;
+
+ /** The attribution tag for this operation */
+ private @Nullable String mAttributionTag;
+
+ /** To call when client dies */
+ private @NonNull Runnable mOnDeath;
+
+ /** uidstate used when calling startOp */
+ private @AppOpsManager.UidState int mUidState;
+
+ /** Proxy information of the startOp event */
+ private @Nullable AppOpsManager.OpEventProxyInfo mProxy;
+
+ /** Proxy flag information */
+ private @AppOpsManager.OpFlags int mFlags;
+
+ /** How many times the op was started but not finished yet */
+ int mNumUnfinishedStarts;
+
+ /** The attribution flags related to this event */
+ private @AppOpsManager.AttributionFlags int mAttributionFlags;
+
+ /** The id of the attribution chain this even is a part of */
+ private int mAttributionChainId;
+
+ /**
+ * Create a new {@link InProgressStartOpEvent}.
+ *
+ * @param startTime The time {@link #startOperation} was called
+ * @param startElapsedTime The elapsed time when {@link #startOperation} was called
+ * @param clientId The client id of the caller of {@link #startOperation}
+ * @param attributionTag The attribution tag for the operation.
+ * @param onDeath The code to execute on client death
+ * @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
+ * @param proxy The proxy information, if {@link #startProxyOperation} was
+ * called
+ * @param flags The trusted/nontrusted/self flags.
+ * @throws RemoteException If the client is dying
+ */
+ InProgressStartOpEvent(long startTime, long startElapsedTime,
+ @NonNull IBinder clientId, @Nullable String attributionTag,
+ @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
+ @Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
+ throws RemoteException {
+ mStartTime = startTime;
+ mStartElapsedTime = startElapsedTime;
+ mClientId = clientId;
+ mAttributionTag = attributionTag;
+ mOnDeath = onDeath;
+ mUidState = uidState;
+ mProxy = proxy;
+ mFlags = flags;
+ mAttributionFlags = attributionFlags;
+ mAttributionChainId = attributionChainId;
+
+ clientId.linkToDeath(this, 0);
+ }
+
+ /** Clean up event */
+ public void finish() {
+ try {
+ mClientId.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // Either not linked, or already unlinked. Either way, nothing to do.
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mOnDeath.run();
+ }
+
+ /**
+ * Reinit existing object with new state.
+ *
+ * @param startTime The time {@link #startOperation} was called
+ * @param startElapsedTime The elapsed time when {@link #startOperation} was called
+ * @param clientId The client id of the caller of {@link #startOperation}
+ * @param attributionTag The attribution tag for this operation.
+ * @param onDeath The code to execute on client death
+ * @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param flags The flags relating to the proxy
+ * @param proxy The proxy information, if {@link #startProxyOperation}
+ * was called
+ * @param attributionFlags the attribution flags for this operation.
+ * @param attributionChainId the unique id of the attribution chain this op is a part of.
+ * @param proxyPool The pool to release
+ * previous {@link AppOpsManager.OpEventProxyInfo} to
+ * @throws RemoteException If the client is dying
+ */
+ public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
+ @Nullable String attributionTag, @NonNull Runnable onDeath,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ @Nullable AppOpsManager.OpEventProxyInfo proxy,
+ @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId,
+ @NonNull Pools.Pool<AppOpsManager.OpEventProxyInfo> proxyPool
+ ) throws RemoteException {
+ mStartTime = startTime;
+ mStartElapsedTime = startElapsedTime;
+ mClientId = clientId;
+ mAttributionTag = attributionTag;
+ mOnDeath = onDeath;
+ mUidState = uidState;
+ mFlags = flags;
+
+ if (mProxy != null) {
+ proxyPool.release(mProxy);
+ }
+ mProxy = proxy;
+ mAttributionFlags = attributionFlags;
+ mAttributionChainId = attributionChainId;
+
+ clientId.linkToDeath(this, 0);
+ }
+
+ /** @return Wall clock time of startOp event */
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ /** @return Elapsed time since boot of startOp event */
+ public long getStartElapsedTime() {
+ return mStartElapsedTime;
+ }
+
+ /** @return Id of the client that started the event */
+ public @NonNull IBinder getClientId() {
+ return mClientId;
+ }
+
+ /** @return uidstate used when calling startOp */
+ public @AppOpsManager.UidState int getUidState() {
+ return mUidState;
+ }
+
+ /** @return proxy tag for the access */
+ public @Nullable AppOpsManager.OpEventProxyInfo getProxy() {
+ return mProxy;
+ }
+
+ /** @return flags used for the access */
+ public @AppOpsManager.OpFlags int getFlags() {
+ return mFlags;
+ }
+
+ /** @return attributoin flags used for the access */
+ public @AppOpsManager.AttributionFlags int getAttributionFlags() {
+ return mAttributionFlags;
+ }
+
+ /** @return attribution chain id for the access */
+ public int getAttributionChainId() {
+ return mAttributionChainId;
+ }
+
+ public void setStartTime(long startTime) {
+ mStartTime = startTime;
+ }
+
+ public void setStartElapsedTime(long startElapsedTime) {
+ mStartElapsedTime = startElapsedTime;
+ }
+ }
+
+ /**
+ * An unsynchronized pool of {@link InProgressStartOpEvent} objects.
+ */
+ static class InProgressStartOpEventPool extends Pools.SimplePool<InProgressStartOpEvent> {
+ private OpEventProxyInfoPool mOpEventProxyInfoPool;
+
+ InProgressStartOpEventPool(OpEventProxyInfoPool opEventProxyInfoPool,
+ int maxUnusedPooledObjects) {
+ super(maxUnusedPooledObjects);
+ this.mOpEventProxyInfoPool = opEventProxyInfoPool;
+ }
+
+ InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
+ @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags
+ int attributionFlags, int attributionChainId) throws RemoteException {
+
+ InProgressStartOpEvent recycled = acquire();
+
+ AppOpsManager.OpEventProxyInfo proxyInfo = null;
+ if (proxyUid != Process.INVALID_UID) {
+ proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
+ proxyAttributionTag);
+ }
+
+ if (recycled != null) {
+ recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
+ uidState, flags, proxyInfo, attributionFlags, attributionChainId,
+ mOpEventProxyInfoPool);
+ return recycled;
+ }
+
+ return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
+ onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
+ }
+ }
+
+ /**
+ * An unsynchronized pool of {@link AppOpsManager.OpEventProxyInfo} objects.
+ */
+ static class OpEventProxyInfoPool extends Pools.SimplePool<AppOpsManager.OpEventProxyInfo> {
+ OpEventProxyInfoPool(int maxUnusedPooledObjects) {
+ super(maxUnusedPooledObjects);
+ }
+
+ AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
+ @Nullable String packageName,
+ @Nullable String attributionTag) {
+ AppOpsManager.OpEventProxyInfo recycled = acquire();
+ if (recycled != null) {
+ recycled.reinit(uid, packageName, attributionTag);
+ return recycled;
+ }
+
+ return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
+ }
+ }
+}