From 8fb6ff27de7b9918ca663d3761bf37e4dfb5f237 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Thu, 10 Nov 2022 15:06:46 -0800 Subject: [PATCH 1/2] Copy AppOpsService into AppOpsCheckingServiceImpl Test: Build Bug: 256730492 Change-Id: I5550b1caa97f7ad2fc0cec79bcba237e1218c663 --- .../server/appop/AppOpsServiceImpl.java | 6523 +++++++++++++++++ .../appop/AppOpsUidStateTrackerImpl2.java | 625 ++ .../android/server/appop/AttributedOp2.java | 870 +++ 3 files changed, 8018 insertions(+) create mode 100644 services/core/java/com/android/server/appop/AppOpsServiceImpl.java create mode 100644 services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java create mode 100644 services/core/java/com/android/server/appop/AttributedOp2.java 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 0000000000000..58cecd00c83dd --- /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 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}. + * + *

(package name, uid) -> callbacks + * + * @see #getAsyncNotedOpsKey(String, int) + */ + @GuardedBy("this") + private final ArrayMap, RemoteCallbackList> + mAsyncOpWatchers = new ArrayMap<>(); + + /** + * Async note-ops collected from {@link #collectAsyncNotedOp} that have not been delivered to a + * callback yet. + * + *

(package name, uid) -> list<ops> + * + * @see #getAsyncNotedOpsKey(String, int) + */ + @GuardedBy("this") + private final ArrayMap, ArrayList> + 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 task = new AsyncTask() { + @Override protected Void doInBackground(Void... params) { + writeState(); + return null; + } + }; + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null); + } + } + }; + + @GuardedBy("this") + @VisibleForTesting + final SparseArray mUidStates = new SparseArray<>(); + + volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); + + /* + * These are app op restrictions imposed per user from various parties. + */ + private final ArrayMap mOpUserRestrictions = + new ArrayMap<>(); + + /* + * These are app op restrictions imposed globally from various parties within the system. + */ + private final ArrayMap 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 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 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 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 { + 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 knownAttributionTags = new ArraySet<>(); + + /** + * Lazily populated cache of valid attributionTags of this package, a set smaller + * than or equal to {@link #knownAttributionTags}. + */ + final @NonNull ArraySet 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 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 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 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 mModeWatchers = new ArrayMap<>(); + final ArrayMap> mActiveWatchers = new ArrayMap<>(); + final ArrayMap> mStartedWatchers = new ArrayMap<>(); + final ArrayMap> 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 dstAttributionTags = new ArrayMap<>(); + ArraySet 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 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 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 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 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 collectOps(Ops pkgOps, int[] ops) { + ArrayList resOps = null; + if (ops == null) { + resOps = new ArrayList<>(); + for (int j=0; j(); + } + resOps.add(getOpEntryForResult(curOp)); + } + } + } + return resOps; + } + + @Nullable + private ArrayList 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 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= 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 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 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 packages = uidState.pkgOps; + final int packageCount = packages.size(); + for (int j = 0; j < packageCount; j++) { + Ops pkgOps = packages.valueAt(j); + ArrayList 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 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 resOps = collectOps(pkgOps, ops); + if (resOps == null) { + return null; + } + ArrayList res = new ArrayList(); + 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 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 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 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 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 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 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 resOps = collectUidOps(uidState, ops); + if (resOps == null) { + return null; + } + ArrayList res = new ArrayList(); + 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 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 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 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 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 addChange(ArrayList 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> addCallbacks( + HashMap> callbacks, + int op, int uid, String packageName, int previousMode, + ArraySet cbs) { + if (cbs == null) { + return callbacks; + } + if (callbacks == null) { + callbacks = new HashMap<>(); + } + final int N = cbs.size(); + for (int i=0; i reports = callbacks.get(cb); + ArrayList 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> callbacks = null; + ArrayList 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 packages = uidState.pkgOps; + Iterator> it = packages.entrySet().iterator(); + boolean uidChanged = false; + while (it.hasNext()) { + Map.Entry 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> ent + : callbacks.entrySet()) { + OnOpModeChangedListener cb = ent.getKey(); + ArrayList reports = ent.getValue(); + for (int i=0; i= 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 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 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 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 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 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 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 key = getAsyncNotedOpsKey(packageName, uid); + + RemoteCallbackList 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 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 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 key = getAsyncNotedOpsKey(packageName, uid); + + verifyAndGetBypass(uid, packageName, null); + + synchronized (this) { + RemoteCallbackList callbacks = mAsyncOpWatchers.get(key); + if (callbacks == null) { + callbacks = new RemoteCallbackList() { + @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 key = getAsyncNotedOpsKey(packageName, uid); + + verifyAndGetBypass(uid, packageName, null); + + synchronized (this) { + RemoteCallbackList callbacks = mAsyncOpWatchers.get(key); + if (callbacks != null) { + callbacks.unregister(callback); + if (callbacks.getRegisteredCallbackCount() == 0) { + mAsyncOpWatchers.remove(key); + } + } + } + } + + @Override + public List 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 dispatchedCallbacks = null; + final int callbackListCount = mActiveWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray 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 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 dispatchedCallbacks = null; + final int callbackListCount = mStartedWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray 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 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 dispatchedCallbacks = null; + final int callbackListCount = mNotedWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray 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 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 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 : " + + 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 : " + + 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 : " + + 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 : " + + 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 : " + + 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 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 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 ops = pkg.getOps(); + for (int j=0; j 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 ] [--attribution ] " + + " "); + pw.println(" Starts a given operation for a particular application."); + pw.println(" stop [--user ] [--attribution ] " + + " "); + pw.println(" Stops a given operation for a particular application."); + pw.println(" set [--user ] <[--uid] PACKAGE | UID> "); + pw.println(" Set the mode for a particular application and operation."); + pw.println(" get [--user ] [--attribution ] " + + "[]"); + pw.println(" Return the mode for a particular application and optional operation."); + pw.println(" query-op [--user ] []"); + pw.println(" Print all packages that currently have the given op in the given mode."); + pw.println(" reset [--user ] []"); + 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(" an Android package name or its UID if prefixed by --uid"); + pw.println(" an AppOps operation."); + pw.println(" one of allow, ignore, deny, or default"); + pw.println(" 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 ops = new ArrayList<>(); + if (shell.packageName != null) { + // Uid mode overrides package mode, so make sure it's also reported + List 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 entries = packageOps.getOps(); + for (int j=0; j 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 entries = ops.get(i).getOps(); + for (int j=0; j 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 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 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 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 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= 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 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 packageOps = getOpsForPackage(proxiedUid, + proxiedPackageName, new int[] {op}); + if (packageOps == null || packageOps.isEmpty()) { + return false; + } + final List 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 getPackageListAndResample() { + List packageNames = getPackageNamesForSampling(); + synchronized (this) { + resamplePackageAndAppOpLocked(packageNames); + } + return packageNames; + } + + /** Resamples package and appop to watch from the list provided. */ + private void resamplePackageAndAppOpLocked(@NonNull List 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 candidates) { + AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); + List 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() { + @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 getRuntimeAppOpsList() { + ArrayList 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 getPackageNamesForSampling() { + List 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 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 0000000000000..96048f3b7ed0a --- /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 + 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 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 0000000000000..16fa7e185f94c --- /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 + * + *

Key is {@link AppOpsManager#makeKey} + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsCheckingServiceImpl") + private @Nullable LongSparseArray mAccessEvents; + + /** + * Last rejected accesses for each uidState/opFlag combination + * + *

Key is {@link AppOpsManager#makeKey} + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsCheckingServiceImpl") + private @Nullable LongSparseArray mRejectEvents; + + /** + * Currently in progress startOp events + * + *

Key is clientId + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsCheckingServiceImpl") + @Nullable ArrayMap mInProgressEvents; + + /** + * Currently paused startOp events + * + *

Key is clientId + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsCheckingServiceImpl") + @Nullable ArrayMap 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 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 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 events = + isRunning ? mInProgressEvents : mPausedInProgressEvents; + + int numInProgressEvents = events.size(); + List 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 add( + @Nullable LongSparseArray a, + @Nullable LongSparseArray 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. + * + *

If there is an event for the same key in both the later event is retained. + *

{@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 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 deepClone( + @Nullable LongSparseArray original) { + if (original == null) { + return original; + } + + int size = original.size(); + LongSparseArray 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 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 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 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 { + 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 { + 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); + } + } +} From 313162bfc6ddf9c35d7a765a3f5d86c14ce5692d Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Thu, 10 Nov 2022 15:10:10 -0800 Subject: [PATCH 2/2] Move most of AppOpsService implementation behind an interface This moves almost everything about appops besides audio restriction stuff and the check op delegate. Test: In progress Bug:256730492 Change-Id: Id642f8369959defb93f9f729c68d11f85b992af2 --- ...pl.java => AppOpsCheckingServiceImpl.java} | 14 +- .../appop/AppOpsCheckingServiceInterface.java | 209 + .../server/appop/AppOpsRestrictionsImpl.java | 4 +- .../android/server/appop/AppOpsService.java | 4579 +---------------- .../server/appop/AppOpsServiceImpl.java | 2516 ++------- .../server/appop/AppOpsServiceInterface.java | 547 +- .../appop/AppOpsUidStateTrackerImpl.java | 6 +- .../appop/AppOpsUidStateTrackerImpl2.java | 625 --- .../android/server/appop/AttributedOp.java | 78 +- .../android/server/appop/AttributedOp2.java | 870 ---- .../appop/AppOpsLegacyRestrictionsTest.java | 4 +- .../server/appop/AppOpsServiceTest.java | 71 +- .../appop/AppOpsUidStateTrackerTest.java | 2 +- .../server/appop/AppOpsUpgradeTest.java | 15 +- 14 files changed, 1323 insertions(+), 8217 deletions(-) rename services/core/java/com/android/server/appop/{LegacyAppOpsServiceInterfaceImpl.java => AppOpsCheckingServiceImpl.java} (97%) create mode 100644 services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java delete mode 100644 services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java delete mode 100644 services/core/java/com/android/server/appop/AttributedOp2.java diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java similarity index 97% rename from services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java rename to services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java index f6fff351c2323..8d1da71c95c18 100644 --- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java @@ -20,7 +20,7 @@ import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager.opRestrictsRead; -import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; +import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS; import android.Manifest; import android.annotation.NonNull; @@ -56,7 +56,7 @@ import java.util.Objects; * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access. * In the future this class will also include mode callbacks and op restrictions. */ -public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface { +public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface { static final String TAG = "LegacyAppOpsServiceInterfaceImpl"; @@ -84,9 +84,9 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface private static final int UID_ANY = -2; - LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler, - @NonNull Object lock, Handler handler, Context context, - SparseArray switchedOps) { + AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler, + @NonNull Object lock, Handler handler, Context context, + SparseArray switchedOps) { this.mPersistenceScheduler = persistenceScheduler; this.mLock = lock; this.mHandler = handler; @@ -456,7 +456,7 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface final ArraySet reportedPackageNames = callbackSpecs.valueAt(i); if (reportedPackageNames == null) { mHandler.sendMessage(PooledLambda.obtainMessage( - LegacyAppOpsServiceInterfaceImpl::notifyOpChanged, + AppOpsCheckingServiceImpl::notifyOpChanged, this, callback, code, uid, (String) null)); } else { @@ -464,7 +464,7 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface for (int j = 0; j < reportedPackageCount; j++) { final String reportedPackageName = reportedPackageNames.valueAt(j); mHandler.sendMessage(PooledLambda.obtainMessage( - LegacyAppOpsServiceInterfaceImpl::notifyOpChanged, + AppOpsCheckingServiceImpl::notifyOpChanged, this, callback, code, uid, reportedPackageName)); } } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java new file mode 100644 index 0000000000000..d8d0d48965eac --- /dev/null +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.appop; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppOpsManager.Mode; +import android.util.ArraySet; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; + +import java.io.PrintWriter; + +/** + * Interface for accessing and modifying modes for app-ops i.e. package and uid modes. + * This interface also includes functions for added and removing op mode watchers. + * In the future this interface will also include op restrictions. + */ +public interface AppOpsCheckingServiceInterface { + /** + * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid. + * Returns an empty SparseIntArray if nothing is set. + * @param uid for which we need the app-ops and their modes. + */ + SparseIntArray getNonDefaultUidModes(int uid); + + /** + * Returns the app-op mode for a particular app-op of a uid. + * Returns default op mode if the op mode for particular uid and op is not set. + * @param uid user id for which we need the mode. + * @param op app-op for which we need the mode. + * @return mode of the app-op. + */ + int getUidMode(int uid, int op); + + /** + * Set the app-op mode for a particular uid and op. + * The mode is not set if the mode is the same as the default mode for the op. + * @param uid user id for which we want to set the mode. + * @param op app-op for which we want to set the mode. + * @param mode mode for the app-op. + * @return true if op mode is changed. + */ + boolean setUidMode(int uid, int op, @Mode int mode); + + /** + * Gets the app-op mode for a particular package. + * Returns default op mode if the op mode for the particular package is not set. + * @param packageName package name for which we need the op mode. + * @param op app-op for which we need the mode. + * @param userId user id associated with the package. + * @return the mode of the app-op. + */ + int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId); + + /** + * Sets the app-op mode for a particular package. + * @param packageName package name for which we need to set the op mode. + * @param op app-op for which we need to set the mode. + * @param mode the mode of the app-op. + * @param userId user id associated with the package. + * + */ + void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId); + + /** + * Stop tracking any app-op modes for a package. + * @param packageName Name of the package for which we want to remove all mode tracking. + * @param userId user id associated with the package. + */ + boolean removePackage(@NonNull String packageName, @UserIdInt int userId); + + /** + * Stop tracking any app-op modes for this uid. + * @param uid user id for which we want to remove all tracking. + */ + void removeUid(int uid); + + /** + * Returns true if all uid modes for this uid are + * in default state. + * @param uid user id + */ + boolean areUidModesDefault(int uid); + + /** + * Returns true if all package modes for this package name are + * in default state. + * @param packageName package name. + * @param userId user id associated with the package. + */ + boolean arePackageModesDefault(String packageName, @UserIdInt int userId); + + /** + * Stop tracking app-op modes for all uid and packages. + */ + void clearAllModes(); + + /** + * Registers changedListener to listen to op's mode change. + * @param changedListener the listener that must be trigger on the op's mode change. + * @param op op representing the app-op whose mode change needs to be listened to. + */ + void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); + + /** + * Registers changedListener to listen to package's app-op's mode change. + * @param changedListener the listener that must be trigger on the mode change. + * @param packageName of the package whose app-op's mode change needs to be listened to. + */ + void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName); + + /** + * Stop the changedListener from triggering on any mode change. + * @param changedListener the listener that needs to be removed. + */ + void removeListener(@NonNull OnOpModeChangedListener changedListener); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for op's mode changes. + * @param op app-op whose mode change is being listened to. + */ + ArraySet getOpModeChangedListeners(int op); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes. + * @param packageName of package whose app-op's mode change is being listened to. + */ + ArraySet getPackageModeChangedListeners(@NonNull String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed by triggering the change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users) + */ + void notifyWatchersOfChange(int op, int uid); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed by triggering the change listener. + * @param changedListener the change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param packageName package name that is associated with the app-op + */ + void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, + @Nullable String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed to all packages associated with the uid by + * triggering the appropriate change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param onlyForeground true if only watchers that + * @param callbackToIgnore callback that should be ignored. + */ + void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore); + + /** + * TODO: Move hasForegroundWatchers and foregroundOps into this. + * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param uid for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps); + + /** + * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param packageName for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @param userId user id associated with the package. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps, @UserIdInt int userId); + + /** + * Dump op mode and package mode listeners and their details. + * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an + * app-op, only the watchers for that app-op are dumped. + * @param dumpUid uid for which we want to dump op mode watchers. + * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name. + * @param printWriter writer to dump to. + */ + boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter); +} diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java index adfd2afffd78e..af5b07e0bffce 100644 --- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java @@ -42,7 +42,7 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { private Context mContext; private Handler mHandler; - private AppOpsServiceInterface mAppOpsServiceInterface; + private AppOpsCheckingServiceInterface mAppOpsServiceInterface; // Map from (Object token) to (int code) to (boolean restricted) private final ArrayMap mGlobalRestrictions = new ArrayMap<>(); @@ -56,7 +56,7 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { mUserRestrictionExcludedPackageTags = new ArrayMap<>(); public AppOpsRestrictionsImpl(Context context, Handler handler, - AppOpsServiceInterface appOpsServiceInterface) { + AppOpsCheckingServiceInterface appOpsServiceInterface) { mContext = context; mHandler = handler; mAppOpsServiceInterface = appOpsServiceInterface; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7e00c3212c95b..39338c6f43ad3 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -18,55 +18,22 @@ 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; @@ -75,21 +42,16 @@ 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; @@ -97,15 +59,11 @@ 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; @@ -116,22 +74,14 @@ 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; @@ -143,59 +93,37 @@ 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 AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler { +/** + * The system service component to {@link AppOpsManager}. + */ +public class AppOpsService extends IAppOpsService.Stub { + + private final AppOpsServiceInterface mAppOpsService; + static final String TAG = "AppOps"; static final boolean DEBUG = false; @@ -204,57 +132,19 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ private final ArraySet 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 AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new - * objects - */ - @GuardedBy("this") - final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool = - new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS); - - /** - * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate - * new objects - */ - @GuardedBy("this") - final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool = - new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool, - MAX_UNUSED_POOLED_OBJECTS); - private final AppOpsManagerInternalImpl mAppOpsManagerInternal = new AppOpsManagerInternalImpl(); - @Nullable private final DevicePolicyManagerInternal dpmi = - LocalServices.getService(DevicePolicyManagerInternal.class); - - private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); /** * Registered callbacks, called from {@link #collectAsyncNotedOp}. @@ -281,54 +171,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch boolean mWriteNoteOpsScheduled; - boolean mWriteScheduled; - boolean mFastWriteScheduled; - final Runnable mWriteRunner = new Runnable() { - public void run() { - synchronized (AppOpsService.this) { - mWriteScheduled = false; - mFastWriteScheduled = false; - AsyncTask task = new AsyncTask() { - @Override protected Void doInBackground(Void... params) { - writeState(); - return null; - } - }; - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null); - } - } - }; - - @GuardedBy("this") - @VisibleForTesting - final SparseArray mUidStates = new SparseArray<>(); - - volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); - - /* - * These are app op restrictions imposed per user from various parties. - */ - private final ArrayMap mOpUserRestrictions = - new ArrayMap<>(); - - /* - * These are app op restrictions imposed globally from various parties within the system. - */ - private final ArrayMap 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 mSwitchedOps = new SparseArray<>(); - - private ActivityManagerInternal mActivityManagerInternal; /** Package sampled for message collection in the current session */ @GuardedBy("this") @@ -362,545 +207,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** 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 AppOpsUidStateTrackerImpl( - LocalServices.getService(ActivityManagerInternal.class), - mHandler, - r -> { - synchronized (AppOpsService.this) { - r.run(); - } - }, - Clock.SYSTEM_CLOCK, mConstants); - - mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler), - this::onUidStateChanged); - } - return mUidStateTracker; - } - - /** - * All times are in milliseconds. These constants are kept synchronized with the system - * global Settings. Any access to this class or its fields should be done while - * holding the AppOpsService lock. - */ - final class Constants extends ContentObserver { - - /** - * How long we want for a drop in uid state from top to settle before applying it. - * @see Settings.Global#APP_OPS_CONSTANTS - * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME - */ - public long TOP_STATE_SETTLE_TIME; - - /** - * How long we want for a drop in uid state from foreground to settle before applying it. - * @see Settings.Global#APP_OPS_CONSTANTS - * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME - */ - public long FG_SERVICE_STATE_SETTLE_TIME; - - /** - * How long we want for a drop in uid state from background to settle before applying it. - * @see Settings.Global#APP_OPS_CONSTANTS - * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME - */ - public long BG_STATE_SETTLE_TIME; - - private final KeyValueListParser mParser = new KeyValueListParser(','); - private ContentResolver mResolver; - - public Constants(Handler handler) { - super(handler); - updateConstants(); - } - - public void startMonitoring(ContentResolver resolver) { - mResolver = resolver; - mResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS), - false, this); - updateConstants(); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - updateConstants(); - } - - private void updateConstants() { - String value = mResolver != null ? Settings.Global.getString(mResolver, - Settings.Global.APP_OPS_CONSTANTS) : ""; - - synchronized (AppOpsService.this) { - try { - mParser.setString(value); - } catch (IllegalArgumentException e) { - // Failed to parse the settings string, log this and move on - // with defaults. - Slog.e(TAG, "Bad app ops settings", e); - } - TOP_STATE_SETTLE_TIME = mParser.getDurationMillis( - KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L); - FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis( - KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L); - BG_STATE_SETTLE_TIME = mParser.getDurationMillis( - KEY_BG_STATE_SETTLE_TIME, 1 * 1000L); - } - } - - void dump(PrintWriter pw) { - pw.println(" Settings:"); - - pw.print(" "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("="); - TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw); - pw.println(); - pw.print(" "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("="); - TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw); - pw.println(); - pw.print(" "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("="); - TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw); - pw.println(); - } - } - - @VisibleForTesting - final Constants mConstants; - - @VisibleForTesting - final class UidState { - public final int uid; - - public ArrayMap 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 { - 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 knownAttributionTags = new ArraySet<>(); - - /** - * Lazily populated cache of valid attributionTags of this package, a set smaller - * than or equal to {@link #knownAttributionTags}. - */ - final @NonNull ArraySet 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 -> AttributedOp */ - final ArrayMap 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 AttributedOp getOrCreateAttribution(@NonNull Op parent, - @Nullable String attributionTag) { - AttributedOp attributedOp; - - attributedOp = mAttributions.get(attributionTag); - if (attributedOp == null) { - attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent); - mAttributions.put(attributionTag, attributedOp); - } - - return attributedOp; - } - - @NonNull OpEntry createEntryLocked() { - final int numAttributions = mAttributions.size(); - - final ArrayMap 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 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 mModeWatchers = new ArrayMap<>(); - final ArrayMap> mActiveWatchers = new ArrayMap<>(); - final ArrayMap> mStartedWatchers = new ArrayMap<>(); - final ArrayMap> mNotedWatchers = new ArrayMap<>(); final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager(); - final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient { - /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */ - public static final int ALL_OPS = -2; - - // Need to keep this only because stopWatchingMode needs an IAppOpsCallback. - // Otherwise we can just use the IBinder object. - private final IAppOpsCallback mCallback; - - ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode, - int callingUid, int callingPid) { - super(watchingUid, flags, watchedOpCode, callingUid, callingPid); - this.mCallback = callback; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("ModeCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, getWatchingUid()); - sb.append(" flags=0x"); - sb.append(Integer.toHexString(getFlags())); - switch (getWatchedOpCode()) { - case OP_NONE: - break; - case ALL_OPS: - sb.append(" op=(all)"); - break; - default: - sb.append(" op="); - sb.append(opToName(getWatchedOpCode())); - break; - } - sb.append(" from uid="); - UserHandle.formatUid(sb, getCallingUid()); - sb.append(" pid="); - sb.append(getCallingPid()); - sb.append('}'); - return sb.toString(); - } - - void unlinkToDeath() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingMode(mCallback); - } - - @Override - public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException { - mCallback.opChanged(op, uid, packageName); - } - } - - final class ActiveCallback implements DeathRecipient { - final IAppOpsActiveCallback mCallback; - final int mWatchingUid; - final int mCallingUid; - final int mCallingPid; - - ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid, - int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mCallingUid = callingUid; - mCallingPid = callingPid; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("ActiveCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); - sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); - sb.append(" pid="); - sb.append(mCallingPid); - sb.append('}'); - return sb.toString(); - } - - void destroy() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingActive(mCallback); - } - } - - final class StartedCallback implements DeathRecipient { - final IAppOpsStartedCallback mCallback; - final int mWatchingUid; - final int mCallingUid; - final int mCallingPid; - - StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid, - int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mCallingUid = callingUid; - mCallingPid = callingPid; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("StartedCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); - sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); - sb.append(" pid="); - sb.append(mCallingPid); - sb.append('}'); - return sb.toString(); - } - - void destroy() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingStarted(mCallback); - } - } - - final class NotedCallback implements DeathRecipient { - final IAppOpsNotedCallback mCallback; - final int mWatchingUid; - final int mCallingUid; - final int mCallingPid; - - NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid, - int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mCallingUid = callingUid; - mCallingPid = callingPid; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("NotedCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); - sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); - sb.append(" pid="); - sb.append(mCallingPid); - sb.append('}'); - return sb.toString(); - } - - void destroy() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingNoted(mCallback); - } - } - - /** - * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}. - */ - static void onClientDeath(@NonNull AttributedOp attributedOp, - @NonNull IBinder clientId) { - attributedOp.onClientDeath(clientId); - } - - /** * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces} * so that we do not log the same operation twice between instances @@ -925,20 +233,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } public AppOpsService(File storagePath, Handler handler, Context context) { - mContext = context; + this(handler, context, new AppOpsServiceImpl(storagePath, handler, 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"); + @VisibleForTesting + public AppOpsService(Handler handler, Context context, + AppOpsServiceInterface appOpsServiceInterface) { if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) { mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(), "noteOpStackTraces.json"); @@ -946,185 +246,25 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } else { mNoteOpCallerStacktracesFile = null; } + + mAppOpsService = appOpsServiceInterface; + mContext = context; mHandler = handler; - mConstants = new Constants(mHandler); - readState(); } + /** + * Publishes binder and local service. + */ 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 (AppOpsService.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 dstAttributionTags = new ArrayMap<>(); - ArraySet attributionTags = new ArraySet<>(); - attributionTags.add(null); - if (pkg.getAttributions() != null) { - int numAttributions = pkg.getAttributions().size(); - for (int attributionNum = 0; attributionNum < numAttributions; - attributionNum++) { - ParsedAttribution attribution = pkg.getAttributions().get(attributionNum); - attributionTags.add(attribution.getTag()); - - int numInheritFrom = attribution.getInheritFrom().size(); - for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; - inheritFromNum++) { - dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum), - attribution.getTag()); - } - } - } - - synchronized (AppOpsService.this) { - UidState uidState = mUidStates.get(uid); - if (uidState == null || 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); - - AttributedOp newAttributedOp = op.getOrCreateAttribution(op, - newAttributionTag); - newAttributedOp.add(op.mAttributions.valueAt(attributionNum)); - op.mAttributions.removeAt(attributionNum); - - scheduleFastWriteLocked(); - } - } - } - } - } - }; - + /** + * Finishes boot sequence. + */ 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 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 onModeChangedListeners; - synchronized (AppOpsService.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); + mAppOpsService.systemReady(); final IntentFilter packageAddedFilter = new IntentFilter(); packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED); @@ -1132,9 +272,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - final Uri data = intent.getData(); - final String packageName = data.getSchemeSpecificPart(); + final String packageName = intent.getData().getSchemeSpecificPart(); PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId()); if (isSamplingTarget(pi)) { @@ -1169,8 +308,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } }); - - mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } /** @@ -1185,132 +322,18 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate); } + /** + * Notify when a package is removed + */ 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++) { - AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum); - - while (attributedOp.isRunning()) { - attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0)); - } - while (attributedOp.isPaused()) { - attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0)); - } - } - } - } - } - - mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory, - mHistoricalRegistry, uid, packageName)); + mAppOpsService.packageRemoved(uid, packageName); } + /** + * Notify when a uid is removed. + */ 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( - AppOpsService::notifyOpChangedForAllPkgsInUid, - this, code, uidState.uid, true, null)); - } else if (uidState.pkgOps != null) { - final ArraySet 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( - AppOpsService::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++) { - AttributedOp attributedOp = op.mAttributions.valueAt( - attributionNum); - - attributedOp.onUidStateChanged(state); - } - } - } - } - } + mAppOpsService.uidRemoved(uid); } /** @@ -1318,542 +341,60 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ 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); - } - } + mAppOpsService.updateUidProcState(uid, procState, capability); } + /** + * Initiates shutdown. + */ 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(); - } + mAppOpsService.shutdown(); + if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) { writeNoteOps(); } - - mHistoricalRegistry.shutdown(); - } - - private ArrayList collectOps(Ops pkgOps, int[] ops) { - ArrayList resOps = null; - if (ops == null) { - resOps = new ArrayList<>(); - for (int j=0; j(); - } - resOps.add(getOpEntryForResult(curOp)); - } - } - } - return resOps; - } - - @Nullable - private ArrayList 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 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= 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 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 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 packages = uidState.pkgOps; - final int packageCount = packages.size(); - for (int j = 0; j < packageCount; j++) { - Ops pkgOps = packages.valueAt(j); - ArrayList 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; + return mAppOpsService.getPackagesForOps(ops); } @Override public List 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 resOps = collectOps(pkgOps, ops); - if (resOps == null) { - return null; - } - ArrayList res = new ArrayList(); - 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 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); + return mAppOpsService.getOpsForPackage(uid, packageName, ops); } @Override public void getHistoricalOps(int uid, String packageName, String attributionTag, List 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 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()); + mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames, + dataType, filter, beginTimeMillis, endTimeMillis, flags, callback); } @Override public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, List 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 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()); + mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag, + opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback); } @Override public void reloadNonHistoricalState() { - mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS, - Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState"); - writeState(); - readState(); + mAppOpsService.reloadNonHistoricalState(); } @Override public List 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 resOps = collectUidOps(uidState, ops); - if (resOps == null) { - return null; - } - ArrayList res = new ArrayList(); - 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 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); + return mAppOpsService.getUidOps(uid, ops); } @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); - } + mAppOpsService.setUidMode(code, uid, mode, null); } /** @@ -1866,309 +407,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ @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 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 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( - AppOpsService::notifyOpChanged, - this, repCbs, code, uid, packageName)); - } - - notifyOpChangedSync(code, uid, packageName, mode, previousMode); - } - - private void notifyOpChanged(ArraySet 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 addChange(ArrayList 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> addCallbacks( - HashMap> callbacks, - int op, int uid, String packageName, int previousMode, - ArraySet cbs) { - if (cbs == null) { - return callbacks; - } - if (callbacks == null) { - callbacks = new HashMap<>(); - } - final int N = cbs.size(); - for (int i=0; i reports = callbacks.get(cb); - ArrayList 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; - } + mAppOpsService.setMode(code, uid, packageName, mode, null); } @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> callbacks = null; - ArrayList 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 packages = uidState.pkgOps; - Iterator> it = packages.entrySet().iterator(); - boolean uidChanged = false; - while (it.hasNext()) { - Map.Entry 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> ent - : callbacks.entrySet()) { - OnOpModeChangedListener cb = ent.getKey(); - ArrayList reports = ent.getValue(); - for (int i=0; i= 0; uidi--) { - final UidState uidState = mUidStates.valueAt(uidi); - if (uidState.foregroundOps != null) { - uidState.evalForegroundOps(); - } - } + mAppOpsService.resetAllModes(reqUserId, reqPackageName); } @Override @@ -2179,66 +423,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch @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(); - } + mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback); } @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(); - } + mAppOpsService.stopWatchingMode(callback); } + /** + * @return the current {@link CheckOpsDelegate}. + */ public CheckOpsDelegate getAppOpsServiceDelegate() { synchronized (AppOpsService.this) { final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher; @@ -2246,6 +441,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } + /** + * Sets the appops {@link CheckOpsDelegate} + */ public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { synchronized (AppOpsService.this) { final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher; @@ -2269,58 +467,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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()); - } + return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw); } @Override @@ -2340,7 +487,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch @Override public void setAudioRestriction(int code, int usage, int uid, int mode, String[] exceptionPackages) { - enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); + mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(), + Binder.getCallingUid(), uid); verifyIncomingUid(uid); verifyIncomingOp(code); @@ -2348,58 +496,35 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch code, usage, uid, mode, exceptionPackages); mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, code, UID_ANY)); + AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code, + UID_ANY)); } @Override public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) { - enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1); + mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(), + Binder.getCallingUid(), -1); mAudioRestrictionManager.setCameraAudioRestriction(mode); mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, + AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, AppOpsManager.OP_PLAY_AUDIO, UID_ANY)); mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, + AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, 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; - } + return mAppOpsService.checkPackage(uid, packageName); } 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, @@ -2445,13 +570,20 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY; - final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid, + final int proxyReturn = mAppOpsService.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, + proxyFlags); + if (proxyReturn != AppOpsManager.MODE_ALLOWED) { + return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag, proxiedPackageName); } + if (shouldCollectAsyncNotedOp) { + boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid, + resolveProxyPackageName, proxyAttributionTag, null); + collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags, + message, shouldCollectMessage); + } } String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, @@ -2463,9 +595,32 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); + final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid, + resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName, + proxyAttributionTag, proxiedFlags); + + boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid, + resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName); + if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) { + collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags, + message, shouldCollectMessage); + } + + + return new SyncNotedAppOp(result, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, + resolveProxiedPackageName); + } + + 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; } @Override @@ -2479,258 +634,58 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); } + int result = mAppOpsService.noteOperation(code, uid, packageName, + attributionTag, message); + 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); + boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid, + resolvedPackageName, attributionTag, null); + + if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) { + collectAsyncNotedOp(uid, resolvedPackageName, code, + isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF, + message, shouldCollectMessage); } - synchronized (this) { - final Ops ops = getOpsLocked(uid, packageName, attributionTag, - pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); - if (ops == null) { - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - AppOpsManager.MODE_IGNORED); - if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid - + " package " + packageName + "flags: " + - AppOpsManager.flagsToString(flags)); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); - } - final Op op = getOpLocked(ops, code, uid, true); - final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); - if (attributedOp.isRunning()) { - Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code " - + code + " startTime of in progress event=" - + attributedOp.mInProgressEvents.valueAt(0).getStartTime()); - } - - final int switchCode = AppOpsManager.opToSwitch(code); - final UidState uidState = ops.uidState; - if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) { - attributedOp.rejected(uidState.getState(), flags); - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - AppOpsManager.MODE_IGNORED); - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); - } - // If there is a non-default per UID policy (we set UID op mode only if - // non-default) it takes over, otherwise use the per package policy. - if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { - final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode)); - if (uidMode != AppOpsManager.MODE_ALLOWED) { - if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.getState(), flags); - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - uidMode); - return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); - } - } else { - final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) - : op; - final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); - if (mode != AppOpsManager.MODE_ALLOWED) { - if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.getState(), flags); - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - mode); - return new SyncNotedAppOp(mode, code, attributionTag, packageName); - } - } - if (DEBUG) { - Slog.d(TAG, - "noteOperation: allowing code " + code + " uid " + uid + " package " - + packageName + (attributionTag == null ? "" - : "." + attributionTag) + " flags: " - + AppOpsManager.flagsToString(flags)); - } - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - AppOpsManager.MODE_ALLOWED); - attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, - uidState.getState(), - flags); - - if (shouldCollectAsyncNotedOp) { - collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, - shouldCollectMessage); - } - - return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, - packageName); - } + return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null, + resolvedPackageName); } // 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 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); - } - } + mAppOpsService.startWatchingActive(ops, callback); } @Override public void stopWatchingActive(IAppOpsActiveCallback callback) { - if (callback == null) { - return; - } - synchronized (this) { - final SparseArray 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(); - } - } + mAppOpsService.stopWatchingActive(callback); } @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 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); - } - } + mAppOpsService.startWatchingStarted(ops, callback); } @Override public void stopWatchingStarted(IAppOpsStartedCallback callback) { - Objects.requireNonNull(callback, "Callback cannot be null"); - - synchronized (this) { - final SparseArray 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(); - } - } + mAppOpsService.stopWatchingStarted(callback); } @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 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); - } - } + mAppOpsService.startWatchingNoted(ops, callback); } @Override public void stopWatchingNoted(IAppOpsNotedCallback callback) { - Objects.requireNonNull(callback, "Callback cannot be null"); - synchronized (this) { - final SparseArray 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(); - } - } + mAppOpsService.stopWatchingNoted(callback); } /** @@ -2817,7 +772,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int uid = Binder.getCallingUid(); Pair key = getAsyncNotedOpsKey(packageName, uid); - verifyAndGetBypass(uid, packageName, null); + mAppOpsService.verifyPackage(uid, packageName); synchronized (this) { RemoteCallbackList callbacks = mAsyncOpWatchers.get(key); @@ -2847,7 +802,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int uid = Binder.getCallingUid(); Pair key = getAsyncNotedOpsKey(packageName, uid); - verifyAndGetBypass(uid, packageName, null); + mAppOpsService.verifyPackage(uid, packageName); synchronized (this) { RemoteCallbackList callbacks = mAsyncOpWatchers.get(key); @@ -2866,7 +821,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int uid = Binder.getCallingUid(); - verifyAndGetBypass(uid, packageName, null); + mAppOpsService.verifyPackage(uid, packageName); synchronized (this) { return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid)); @@ -2889,54 +844,49 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); } + int result = mAppOpsService.startOperation(clientId, code, uid, packageName, + attributionTag, startIfModeDefault, message, + attributionFlags, attributionChainId); + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); + + boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid, + resolvedPackageName, attributionTag, null); + + if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) { + collectAsyncNotedOp(uid, resolvedPackageName, code, + isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF, + message, shouldCollectMessage); } - // 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); + return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null, + resolvedPackageName); } @Override - public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, + public SyncNotedAppOp startProxyOperation(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); + return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, + attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); } - private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code, + private SyncNotedAppOp startProxyOperationImpl(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(); @@ -2984,147 +934,68 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (!skipProxyOperation) { // Test if the proxied operation will succeed before starting the proxy operation - final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code, + final int testProxiedOp = mAppOpsService.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; + + boolean isTestProxiedAttributionTagValid = + mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName, + proxiedAttributionTag, resolvedProxyPackageName); + + if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) { + return new SyncNotedAppOp(testProxiedOp, code, + isTestProxiedAttributionTagValid ? proxiedAttributionTag : null, + resolvedProxiedPackageName); } final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY; - final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid, + final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null, - proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message, - shouldCollectMessage, proxyAttributionFlags, attributionChainId, + proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId, /*dryRun*/ false); - if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) { - return proxyAppOp; + + boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid, + resolvedProxyPackageName, proxyAttributionTag, null); + + if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) { + return new SyncNotedAppOp(proxyAppOp, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, + resolvedProxyPackageName); + } + + if (shouldCollectAsyncNotedOp) { + collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags, + message, shouldCollectMessage); } } - return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag, - proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, proxiedAttributionFlags, attributionChainId, - /*dryRun*/ false); + final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid, + resolvedProxiedPackageName, proxiedAttributionTag, proxyUid, + resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault, + proxiedAttributionFlags, attributionChainId,/*dryRun*/ false); + + boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid, + resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName); + + if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) { + collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, + proxiedAttributionFlags, message, shouldCollectMessage); + } + + return new SyncNotedAppOp(proxiedAppOp, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, + resolvedProxiedPackageName); } 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 AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); - final UidState uidState = ops.uidState; - isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, - false); - final int switchCode = AppOpsManager.opToSwitch(code); - // If there is a non-default per UID policy (we set UID op mode only if - // non-default) it takes over, otherwise use the per package policy. - if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { - final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode)); - if (!shouldStartForMode(uidMode, startIfModeDefault)) { - if (DEBUG) { - Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - } - if (!dryRun) { - attributedOp.rejected(uidState.getState(), flags); - scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, - flags, uidMode, startType, attributionFlags, attributionChainId); - } - return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); - } - } else { - final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) - : op; - final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); - if (mode != AppOpsManager.MODE_ALLOWED - && (!startIfModeDefault || mode != MODE_DEFAULT)) { - if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - if (!dryRun) { - attributedOp.rejected(uidState.getState(), flags); - scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, - flags, mode, startType, attributionFlags, attributionChainId); - } - return new SyncNotedAppOp(mode, code, attributionTag, packageName); - } - } - if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid - + " package " + packageName + " restricted: " + isRestricted - + " flags: " + AppOpsManager.flagsToString(flags)); - if (!dryRun) { - try { - if (isRestricted) { - attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.getState(), flags, - attributionFlags, attributionChainId); - } else { - attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.getState(), flags, - attributionFlags, attributionChainId); - startType = START_TYPE_STARTED; - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } - scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, - isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags, - attributionChainId); - } - } - - if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) { - collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF, - message, shouldCollectMessage); - } - - return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag, - packageName); - } - @Override public void finishOperation(IBinder clientId, int code, int uid, String packageName, String attributionTag) { @@ -3134,22 +1005,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); + mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag); } @Override - public void finishProxyOperation(@NonNull IBinder clientId, int code, + public void finishProxyOperation(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource, skipProxyOperation); @@ -3181,8 +1041,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } if (!skipProxyOperation) { - finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, - proxyAttributionTag); + mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid, + resolvedProxyPackageName, proxyAttributionTag); } String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, @@ -3191,209 +1051,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return null; } - finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag); + mAppOpsService.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 AttributedOp attributedOp = op.mAttributions.get(attributionTag); - if (attributedOp == null) { - Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "(" - + attributionTag + ") op=" + AppOpsManager.opToName(code)); - return; - } - - if (attributedOp.isRunning() || attributedOp.isPaused()) { - attributedOp.finished(clientId); - } else { - Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "(" - + attributionTag + ") op=" + AppOpsManager.opToName(code)); - } - } - } - - void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull - String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags - int attributionFlags, int attributionChainId) { - ArraySet dispatchedCallbacks = null; - final int callbackListCount = mActiveWatchers.size(); - for (int i = 0; i < callbackListCount; i++) { - final SparseArray callbacks = mActiveWatchers.valueAt(i); - ActiveCallback callback = callbacks.get(code); - if (callback != null) { - if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - continue; - } - if (dispatchedCallbacks == null) { - dispatchedCallbacks = new ArraySet<>(); - } - dispatchedCallbacks.add(callback); - } - } - if (dispatchedCallbacks == null) { - return; - } - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpActiveChanged, - this, dispatchedCallbacks, code, uid, packageName, attributionTag, active, - attributionFlags, attributionChainId)); - } - - private void notifyOpActiveChanged(ArraySet 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 dispatchedCallbacks = null; - final int callbackListCount = mStartedWatchers.size(); - for (int i = 0; i < callbackListCount; i++) { - final SparseArray callbacks = mStartedWatchers.valueAt(i); - - StartedCallback callback = callbacks.get(code); - if (callback != null) { - if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - continue; - } - - if (dispatchedCallbacks == null) { - dispatchedCallbacks = new ArraySet<>(); - } - dispatchedCallbacks.add(callback); - } - } - - if (dispatchedCallbacks == null) { - return; - } - - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpStarted, - this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags, - result, startedType, attributionFlags, attributionChainId)); - } - - private void notifyOpStarted(ArraySet 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 dispatchedCallbacks = null; - final int callbackListCount = mNotedWatchers.size(); - for (int i = 0; i < callbackListCount; i++) { - final SparseArray callbacks = mNotedWatchers.valueAt(i); - final NotedCallback callback = callbacks.get(code); - if (callback != null) { - if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - continue; - } - if (dispatchedCallbacks == null) { - dispatchedCallbacks = new ArraySet<>(); - } - dispatchedCallbacks.add(callback); - } - } - if (dispatchedCallbacks == null) { - return; - } - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChecked, - this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags, - result)); - } - - private void notifyOpChecked(ArraySet 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) { @@ -3451,13 +1114,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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. @@ -3498,35 +1154,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch || 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 uidPackageNames, boolean visible) { - synchronized (this) { - getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible); - } - } - /** * @return {@link PackageManagerInternal} */ @@ -3538,764 +1165,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 : " - + 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 : " - + 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 : " - + 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 : " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - uidState.evalForegroundOps(); - } - - private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent, - @Nullable String attribution) - throws NumberFormatException, IOException, XmlPullParserException { - final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution); - - final long key = parser.getAttributeLong(null, "n"); - final int uidState = extractUidStateFromKey(key); - final int opFlags = extractFlagsFromKey(key); - - final long accessTime = parser.getAttributeLong(null, "t", 0); - final long rejectTime = parser.getAttributeLong(null, "r", 0); - final long accessDuration = parser.getAttributeLong(null, "d", -1); - final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp"); - final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID); - final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc"); - - if (accessTime > 0) { - attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg, - proxyAttributionTag, uidState, opFlags); - } - if (rejectTime > 0) { - attributedOp.rejected(rejectTime, uidState, opFlags); - } - } - - private void readOp(TypedXmlPullParser parser, - @NonNull UidState uidState, @NonNull String pkgName) - throws NumberFormatException, XmlPullParserException, IOException { - int opCode = parser.getAttributeInt(null, "n"); - Op op = new Op(uidState, pkgName, opCode, uidState.uid); - - 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 : " - + 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 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 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 ops = pkg.getOps(); - for (int j=0; j 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 AppOpsService mInternal; @@ -4309,7 +1178,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int mode; int packageUid; int nonpackageUid; - final static Binder sBinder = new Binder(); IBinder mToken; boolean targetsUid; @@ -4330,7 +1198,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch dumpCommandHelp(pw); } - static private int strOpToOp(String op, PrintWriter err) { + static int strOpToOp(String op, PrintWriter err) { try { return AppOpsManager.strOpToOp(op); } catch (IllegalArgumentException e) { @@ -4527,6 +1395,24 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch pw.println(" not specified, the current user is assumed."); } + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mAppOpsService.dump(fd, pw, args); + + 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 (mAudioRestrictionManager.hasActiveRestrictions()) { + pw.println(); + mAudioRestrictionManager.dump(pw); + } + } static int onShellCommand(Shell shell, String cmd) { if (cmd == null) { return shell.handleDefaultCommands(cmd); @@ -4730,14 +1616,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return 0; } case "write-settings": { - shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(), + shell.mInternal.mAppOpsService + .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(); + shell.mInternal.mAppOpsService.writeState(); pw.println("Current settings written."); } finally { Binder.restoreCallingIdentity(token); @@ -4745,11 +1629,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return 0; } case "read-settings": { - shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(), - Binder.getCallingUid(), -1); + shell.mInternal.mAppOpsService + .enforceManageAppOpsModes(Binder.getCallingPid(), + Binder.getCallingUid(), -1); final long token = Binder.clearCallingIdentity(); try { - shell.mInternal.readState(); + shell.mInternal.mAppOpsService.readState(); pw.println("Last settings read."); } finally { Binder.restoreCallingIdentity(token); @@ -4795,877 +1680,70 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 keys = entry.collectKeys(); - - final int keyCount = keys.size(); - for (int k = 0; k < keyCount; k++) { - final long key = keys.valueAt(k); - - final int uidState = AppOpsManager.extractUidStateFromKey(key); - final int flags = AppOpsManager.extractFlagsFromKey(key); - - final long accessTime = entry.getLastAccessTime(uidState, uidState, flags); - final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags); - final long accessDuration = entry.getLastDuration(uidState, uidState, flags); - final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags); - - String proxyPkg = null; - String proxyAttributionTag = null; - int proxyUid = Process.INVALID_UID; - if (proxy != null) { - proxyPkg = proxy.getPackageName(); - proxyAttributionTag = proxy.getAttributionTag(); - proxyUid = proxy.getUid(); - } - - if (accessTime > 0) { - pw.print(prefix); - pw.print("Access: "); - pw.print(AppOpsManager.keyToString(key)); - pw.print(" "); - date.setTime(accessTime); - pw.print(sdf.format(date)); - pw.print(" ("); - TimeUtils.formatDuration(accessTime - now, pw); - pw.print(")"); - if (accessDuration > 0) { - pw.print(" duration="); - TimeUtils.formatDuration(accessDuration, pw); - } - if (proxyUid >= 0) { - pw.print(" proxy["); - pw.print("uid="); - pw.print(proxyUid); - pw.print(", pkg="); - pw.print(proxyPkg); - pw.print(", attributionTag="); - pw.print(proxyAttributionTag); - pw.print("]"); - } - pw.println(); - } - - if (rejectTime > 0) { - pw.print(prefix); - pw.print("Reject: "); - pw.print(AppOpsManager.keyToString(key)); - date.setTime(rejectTime); - pw.print(sdf.format(date)); - pw.print(" ("); - TimeUtils.formatDuration(rejectTime - now, pw); - pw.print(")"); - if (proxyUid >= 0) { - pw.print(" proxy["); - pw.print("uid="); - pw.print(proxyUid); - pw.print(", pkg="); - pw.print(proxyPkg); - pw.print(", attributionTag="); - pw.print(proxyAttributionTag); - pw.print("]"); - } - pw.println(); - } - } - - final AttributedOp attributedOp = op.mAttributions.get(attributionTag); - if (attributedOp.isRunning()) { - long earliestElapsedTime = Long.MAX_VALUE; - long maxNumStarts = 0; - int numInProgressEvents = attributedOp.mInProgressEvents.size(); - for (int i = 0; i < numInProgressEvents; i++) { - AttributedOp.InProgressStartOpEvent event = - attributedOp.mInProgressEvents.valueAt(i); - - earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime()); - maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts); - } - - pw.print(prefix + "Running start at: "); - TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw); - pw.println(); - - if (maxNumStarts > 1) { - pw.print(prefix + "startNesting="); - pw.println(maxNumStarts); - } - } - } - - @NeverCompile // Avoid size overhead of debugging code. - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; - - int dumpOp = OP_NONE; - String dumpPackage = null; - String dumpAttributionTag = null; - int dumpUid = Process.INVALID_UID; - int dumpMode = -1; - boolean dumpWatchers = false; - // TODO ntmyren: Remove the dumpHistory and dumpFilter - boolean dumpHistory = false; - boolean includeDiscreteOps = false; - boolean dumpUidStateChangeLogs = false; - int nDiscreteOps = 10; - @HistoricalOpsRequestFilter int dumpFilter = 0; - boolean dumpAll = false; - - if (args != null) { - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - if ("-h".equals(arg)) { - dumpHelp(pw); - return; - } else if ("-a".equals(arg)) { - // dump all data - dumpAll = true; - } else if ("--op".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --op option"); - return; - } - dumpOp = Shell.strOpToOp(args[i], pw); - dumpFilter |= FILTER_BY_OP_NAMES; - if (dumpOp < 0) { - return; - } - } else if ("--package".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --package option"); - return; - } - dumpPackage = args[i]; - dumpFilter |= FILTER_BY_PACKAGE_NAME; - try { - dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage, - PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT, - 0); - } catch (RemoteException e) { - } - if (dumpUid < 0) { - pw.println("Unknown package: " + dumpPackage); - return; - } - dumpUid = UserHandle.getAppId(dumpUid); - dumpFilter |= FILTER_BY_UID; - } else if ("--attributionTag".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --attributionTag option"); - return; - } - dumpAttributionTag = args[i]; - dumpFilter |= FILTER_BY_ATTRIBUTION_TAG; - } else if ("--mode".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --mode option"); - return; - } - dumpMode = Shell.strModeToMode(args[i], pw); - if (dumpMode < 0) { - return; - } - } else if ("--watchers".equals(arg)) { - dumpWatchers = true; - } else if ("--include-discrete".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --include-discrete option"); - return; - } - try { - nDiscreteOps = Integer.valueOf(args[i]); - } catch (NumberFormatException e) { - pw.println("Wrong parameter: " + args[i]); - return; - } - includeDiscreteOps = true; - } else if ("--history".equals(arg)) { - dumpHistory = true; - } else if (arg.length() > 0 && arg.charAt(0) == '-') { - pw.println("Unknown option: " + arg); - return; - } else if ("--uid-state-changes".equals(arg)) { - dumpUidStateChangeLogs = true; - } else { - pw.println("Unknown command: " + arg); - return; - } - } - } - - final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - final Date date = new Date(); - synchronized (this) { - pw.println("Current AppOps Service state:"); - if (!dumpHistory && !dumpWatchers) { - mConstants.dump(pw); - } - pw.println(); - final long now = System.currentTimeMillis(); - final long nowElapsed = SystemClock.elapsedRealtime(); - final long nowUptime = SystemClock.uptimeMillis(); - boolean needSep = false; - if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers - && !dumpHistory) { - pw.println(" Profile owners:"); - for (int poi = 0; poi < mProfileOwners.size(); poi++) { - pw.print(" User #"); - pw.print(mProfileOwners.keyAt(poi)); - pw.print(": "); - UserHandle.formatUid(pw, mProfileOwners.valueAt(poi)); - pw.println(); - } - pw.println(); - } - - if (!dumpHistory) { - needSep |= 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 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 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 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 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= 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); - } - } + mAppOpsService.setUserRestrictions(restrictions, token, userHandle); } @Override public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, PackageTagsList excludedPackageTags) { - if (Binder.getCallingPid() != Process.myPid()) { - mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS, - Binder.getCallingPid(), Binder.getCallingUid(), null); - } - if (userHandle != UserHandle.getCallingUserId()) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission - .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission - .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or" - + " INTERACT_ACROSS_USERS to interact cross user "); - } - } - verifyIncomingOp(code); - Objects.requireNonNull(token); - setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags); - } - - private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token, - int userHandle, PackageTagsList excludedPackageTags) { - synchronized (AppOpsService.this) { - ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token); - - if (restrictionState == null) { - try { - restrictionState = new ClientUserRestrictionState(token); - } catch (RemoteException e) { - return; - } - mOpUserRestrictions.put(token, restrictionState); - } - - if (restrictionState.setRestriction(code, restricted, excludedPackageTags, - userHandle)) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, code, UID_ANY)); - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::updateStartedOpModeForUser, this, code, restricted, - userHandle)); - } - - if (restrictionState.isDefault()) { - mOpUserRestrictions.remove(token); - restrictionState.destroy(); - } - } - } - - private void updateStartedOpModeForUser(int code, boolean restricted, int userId) { - synchronized (AppOpsService.this) { - int numUids = mUidStates.size(); - for (int uidNum = 0; uidNum < numUids; uidNum++) { - int uid = mUidStates.keyAt(uidNum); - if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) { - continue; - } - updateStartedOpModeForUidLocked(code, restricted, uid); - } - } - } - - private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) { - UidState uidState = mUidStates.get(uid); - if (uidState == null || 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++) { - AttributedOp attrOp = op.mAttributions.valueAt(attrNum); - if (restricted && attrOp.isRunning()) { - attrOp.pause(); - } else if (attrOp.isPaused()) { - attrOp.resume(); - } - } - } - } - - private void notifyWatchersOfChange(int code, int uid) { - final ArraySet modeChangedListenerSet; - synchronized (this) { - modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code); - if (modeChangedListenerSet == null) { - return; - } - } - - notifyOpChanged(modeChangedListenerSet, code, uid, null); + mAppOpsService.setUserRestriction(code, restricted, token, userHandle, + excludedPackageTags); } @Override public void removeUser(int userHandle) throws RemoteException { - checkSystemUid("removeUser"); - synchronized (AppOpsService.this) { - final int tokenCount = mOpUserRestrictions.size(); - for (int i = tokenCount - 1; i >= 0; i--) { - ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i); - opRestrictions.removeUser(userHandle); - } - removeUidsForUserLocked(userHandle); - } + mAppOpsService.removeUser(userHandle); } @Override public boolean isOperationActive(int code, int uid, String packageName) { - if (Binder.getCallingUid() != uid) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) - != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - verifyIncomingOp(code); - if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { - return false; - } - - final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return false; - } - // TODO moltmann: Allow to check for attribution op activeness - synchronized (AppOpsService.this) { - Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false); - if (pkgOps == null) { - return false; - } - - Op op = pkgOps.get(code); - if (op == null) { - return false; - } - - return op.isRunning(); - } + return mAppOpsService.isOperationActive(code, uid, packageName); } @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 packageOps = getOpsForPackage(proxiedUid, - proxiedPackageName, new int[] {op}); - if (packageOps == null || packageOps.isEmpty()) { - return false; - } - final List 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); - } + return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag, + proxiedUid, proxiedPackageName); } @Override public void resetPackageOpsNoHistory(@NonNull String packageName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "resetPackageOpsNoHistory"); - synchronized (AppOpsService.this) { - final int uid = mPackageManagerInternal.getPackageUid(packageName, 0, - UserHandle.getCallingUserId()); - if (uid == Process.INVALID_UID) { - return; - } - UidState uidState = mUidStates.get(uid); - if (uidState == null || uidState.pkgOps == null) { - return; - } - Ops removedOps = uidState.pkgOps.remove(packageName); - mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid)); - if (removedOps != null) { - scheduleFastWriteLocked(); - } - } + mAppOpsService.resetPackageOpsNoHistory(packageName); } @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); + mAppOpsService.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); + mAppOpsService.offsetHistory(offsetMillis); } @Override public void addHistoricalOps(HistoricalOps ops) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "addHistoricalOps"); - // Must not hold the appops lock - mHistoricalRegistry.addHistoricalOps(ops); + mAppOpsService.addHistoricalOps(ops); } @Override public void resetHistoryParameters() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "resetHistoryParameters"); - // Must not hold the appops lock - mHistoricalRegistry.resetHistoryParameters(); + mAppOpsService.resetHistoryParameters(); } @Override public void clearHistory() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "clearHistory"); - // Must not hold the appops lock - mHistoricalRegistry.clearAllHistory(); + mAppOpsService.clearHistory(); } @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(); + mAppOpsService.rebootHistory(offlineDurationMillis); } /** @@ -5920,24 +1998,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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; @@ -5958,184 +2018,43 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return Process.INVALID_UID; } - private static String[] getPackagesForUid(int uid) { - String[] packageNames = null; - - // Very early during boot the package manager is not yet or not yet fully started. At this - // time there are no packages yet. - if (AppGlobals.getPackageManager() != null) { - try { - packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); - } catch (RemoteException e) { - /* ignore - local call */ - } - } - if (packageNames == null) { - return EmptyArray.STRING; - } - return packageNames; - } - - private final class ClientUserRestrictionState implements DeathRecipient { - private final IBinder token; - - ClientUserRestrictionState(IBinder token) - throws RemoteException { - token.linkToDeath(this, 0); - this.token = token; - } - - public boolean setRestriction(int code, boolean restricted, - PackageTagsList excludedPackageTags, int userId) { - return mAppOpsRestrictions.setUserRestriction(token, userId, code, - restricted, excludedPackageTags); - } - - public boolean hasRestriction(int code, String packageName, String attributionTag, - int userId, boolean isCheckOp) { - return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName, - attributionTag, isCheckOp); - } - - public void removeUser(int userId) { - mAppOpsRestrictions.clearUserRestrictions(token, userId); - } - - public boolean isDefault() { - return !mAppOpsRestrictions.hasUserRestrictions(token); - } - - @Override - public void binderDied() { - synchronized (AppOpsService.this) { - mAppOpsRestrictions.clearUserRestrictions(token); - mOpUserRestrictions.remove(token); - destroy(); - } - } - - public void destroy() { - token.unlinkToDeath(this, 0); - } - } - - private final class ClientGlobalRestrictionState implements DeathRecipient { - final IBinder mToken; - - ClientGlobalRestrictionState(IBinder token) - throws RemoteException { - token.linkToDeath(this, 0); - this.mToken = token; - } - - boolean setRestriction(int code, boolean restricted) { - return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted); - } - - boolean hasRestriction(int code) { - return mAppOpsRestrictions.getGlobalRestriction(mToken, code); - } - - boolean isDefault() { - return !mAppOpsRestrictions.hasGlobalRestrictions(mToken); - } - - @Override - public void binderDied() { - mAppOpsRestrictions.clearGlobalRestrictions(mToken); - mOpGlobalRestrictions.remove(mToken); - destroy(); - } - - void destroy() { - mToken.unlinkToDeath(this, 0); - } - } - private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal { @Override public void setDeviceAndProfileOwners(SparseIntArray owners) { - synchronized (AppOpsService.this) { - mProfileOwners = owners; - } + AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners); } @Override public void updateAppWidgetVisibility(SparseArray uidPackageNames, boolean visible) { - AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible); + AppOpsService.this.mAppOpsService + .updateAppWidgetVisibility(uidPackageNames, visible); } @Override public void setUidModeFromPermissionPolicy(int code, int uid, int mode, @Nullable IAppOpsCallback callback) { - setUidMode(code, uid, mode, callback); + AppOpsService.this.mAppOpsService.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); + AppOpsService.this.mAppOpsService + .setMode(code, uid, packageName, mode, callback); } @Override public void setGlobalRestriction(int code, boolean restricted, IBinder token) { - if (Binder.getCallingPid() != Process.myPid()) { - // TODO instead of this enforcement put in AppOpsManagerInternal - throw new SecurityException("Only the system can set global restrictions"); - } - - synchronized (AppOpsService.this) { - ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token); - - if (restrictionState == null) { - try { - restrictionState = new ClientGlobalRestrictionState(token); - } catch (RemoteException e) { - return; - } - mOpGlobalRestrictions.put(token, restrictionState); - } - - if (restrictionState.setRestriction(code, restricted)) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, AppOpsService.this, code, - UID_ANY)); - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::updateStartedOpModeForUser, AppOpsService.this, - code, restricted, UserHandle.USER_ALL)); - } - - if (restrictionState.isDefault()) { - mOpGlobalRestrictions.remove(token); - restrictionState.destroy(); - } - } + AppOpsService.this.mAppOpsService + .setGlobalRestriction(code, restricted, token); } @Override public int getOpRestrictionCount(int code, UserHandle user, String pkg, String attributionTag) { - int number = 0; - synchronized (AppOpsService.this) { - int numRestrictions = mOpUserRestrictions.size(); - for (int i = 0; i < numRestrictions; i++) { - if (mOpUserRestrictions.valueAt(i) - .hasRestriction(code, pkg, attributionTag, user.getIdentifier(), - false)) { - number++; - } - } - - numRestrictions = mOpGlobalRestrictions.size(); - for (int i = 0; i < numRestrictions; i++) { - if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) { - number++; - } - } - } - - return number; + return AppOpsService.this.mAppOpsService + .getOpRestrictionCount(code, user, pkg, attributionTag); } } @@ -6431,7 +2350,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl); } - public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, + public SyncNotedAppOp startProxyOperation(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @@ -6461,7 +2380,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); } - private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code, + private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @@ -6495,7 +2414,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch AppOpsService.this::finishOperationImpl); } - public void finishProxyOperation(@NonNull IBinder clientId, int code, + public void finishProxyOperation(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { @@ -6513,7 +2432,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code, + private Void finishDelegateProxyOperationImpl(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource, skipProxyOperation, AppOpsService.this::finishProxyOperationImpl); diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java index 58cecd00c83dd..70f3bcc64ef0c 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java @@ -16,14 +16,15 @@ 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.AttributedOpEntry; +import static android.app.AppOpsManager.AttributionFlags; 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.HistoricalOps; 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; @@ -33,6 +34,7 @@ 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.Mode; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; @@ -45,12 +47,10 @@ 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.OpEntry; import static android.app.AppOpsManager.OpEventProxyInfo; +import static android.app.AppOpsManager.OpFlags; 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; @@ -59,13 +59,10 @@ 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 static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS; import android.Manifest; import android.annotation.NonNull; @@ -75,30 +72,16 @@ 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; @@ -107,15 +90,12 @@ import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; +import android.os.IBinder.DeathRecipient; 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; @@ -125,7 +105,6 @@ 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; @@ -134,15 +113,11 @@ 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; @@ -155,18 +130,13 @@ 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; @@ -175,12 +145,9 @@ 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; @@ -190,27 +157,21 @@ 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 { +class AppOpsServiceImpl implements AppOpsServiceInterface { 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 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 */ + /** + * 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; + 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; @@ -221,66 +182,35 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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 + * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new * objects */ @GuardedBy("this") - final AttributedOp2.OpEventProxyInfoPool mOpEventProxyInfoPool = - new AttributedOp2.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS); + final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool = + new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS); /** - * Pool for {@link AttributedOp2.InProgressStartOpEventPool} to avoid to constantly reallocate + * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate * new objects */ @GuardedBy("this") - final AttributedOp2.InProgressStartOpEventPool mInProgressStartOpEventPool = - new AttributedOp2.InProgressStartOpEventPool(mOpEventProxyInfoPool, + final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool = + new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool, MAX_UNUSED_POOLED_OBJECTS); - - private final AppOpsManagerInternalImpl mAppOpsManagerInternal - = new AppOpsManagerInternalImpl(); - @Nullable private final DevicePolicyManagerInternal dpmi = + @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}. - * - *

(package name, uid) -> callbacks - * - * @see #getAsyncNotedOpsKey(String, int) - */ - @GuardedBy("this") - private final ArrayMap, RemoteCallbackList> - mAsyncOpWatchers = new ArrayMap<>(); - - /** - * Async note-ops collected from {@link #collectAsyncNotedOp} that have not been delivered to a - * callback yet. - * - *

(package name, uid) -> list<ops> - * - * @see #getAsyncNotedOpsKey(String, int) - */ - @GuardedBy("this") - private final ArrayMap, ArrayList> - mUnforwardedAsyncNotedOps = new ArrayMap<>(); - - boolean mWriteNoteOpsScheduled; - boolean mWriteScheduled; boolean mFastWriteScheduled; final Runnable mWriteRunner = new Runnable() { @@ -289,12 +219,13 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc mWriteScheduled = false; mFastWriteScheduled = false; AsyncTask task = new AsyncTask() { - @Override protected Void doInBackground(Void... params) { + @Override + protected Void doInBackground(Void... params) { writeState(); return null; } }; - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); } } }; @@ -319,62 +250,38 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc SparseIntArray mProfileOwners; - private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher = - new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null); - /** - * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never - * changed - */ + * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never + * changed + */ private final SparseArray 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 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()} */ + /** + * Package Manager internal. Access via {@link #getPackageManagerInternal()} + */ private @Nullable PackageManagerInternal mPackageManagerInternal; - /** Interface for app-op modes.*/ - @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface; + /** + * Interface for app-op modes. + */ + @VisibleForTesting + AppOpsCheckingServiceInterface mAppOpsServiceInterface; - /** Interface for app-op restrictions.*/ - @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions; + /** + * Interface for app-op restrictions. + */ + @VisibleForTesting + AppOpsRestrictions mAppOpsRestrictions; private AppOpsUidStateTracker mUidStateTracker; - /** Hands the definition of foreground and uid states */ + /** + * Hands the definition of foreground and uid states + */ @GuardedBy("this") public AppOpsUidStateTracker getUidStateTracker() { if (mUidStateTracker == null) { - mUidStateTracker = new AppOpsUidStateTrackerImpl2( + mUidStateTracker = new AppOpsUidStateTrackerImpl( LocalServices.getService(ActivityManagerInternal.class), mHandler, r -> { @@ -399,6 +306,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc /** * 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 */ @@ -406,6 +314,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc /** * 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 */ @@ -413,6 +322,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc /** * 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 */ @@ -421,7 +331,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc private final KeyValueListParser mParser = new KeyValueListParser(','); private ContentResolver mResolver; - public Constants(Handler handler) { + Constants(Handler handler) { super(handler); updateConstants(); } @@ -463,13 +373,19 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc void dump(PrintWriter pw) { pw.println(" Settings:"); - pw.print(" "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("="); + 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("="); + 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("="); + pw.print(" "); + pw.print(KEY_BG_STATE_SETTLE_TIME); + pw.print("="); TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw); pw.println(); } @@ -543,13 +459,14 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (pkgOps != null) { for (int i = pkgOps.size() - 1; i >= 0; i--) { foregroundOps = mAppOpsServiceInterface - .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps, + .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, + foregroundOps, UserHandle.getUserId(uid)); } } hasForegroundWatchers = false; if (foregroundOps != null) { - for (int i = 0; i < foregroundOps.size(); i++) { + for (int i = 0; i < foregroundOps.size(); i++) { if (foregroundOps.valueAt(i)) { hasForegroundWatchers = true; break; @@ -569,7 +486,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - final static class Ops extends SparseArray { + static final class Ops extends SparseArray { final String packageName; final UidState uidState; @@ -612,8 +529,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc final UidState uidState; final @NonNull String packageName; - /** attributionTag -> AttributedOp2 */ - final ArrayMap mAttributions = new ArrayMap<>(1); + /** attributionTag -> AttributedOp */ + final ArrayMap mAttributions = new ArrayMap<>(1); Op(UidState uidState, String packageName, int op, int uid) { this.op = op; @@ -626,6 +543,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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)); @@ -639,20 +557,22 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - private @NonNull AttributedOp2 getOrCreateAttribution(@NonNull Op parent, + private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent, @Nullable String attributionTag) { - AttributedOp2 attributedOp; + AttributedOp attributedOp; attributedOp = mAttributions.get(attributionTag); if (attributedOp == null) { - attributedOp = new AttributedOp2(AppOpsServiceImpl.this, attributionTag, parent); + attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag, + parent); mAttributions.put(attributionTag, attributedOp); } return attributedOp; } - @NonNull OpEntry createEntryLocked() { + @NonNull + OpEntry createEntryLocked() { final int numAttributions = mAttributions.size(); final ArrayMap attributionEntries = @@ -665,7 +585,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc return new OpEntry(op, getMode(), attributionEntries); } - @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) { + @NonNull + OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) { final int numAttributions = mAttributions.size(); final ArrayMap attributionEntries = new ArrayMap<>(1); @@ -696,7 +617,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc final ArrayMap> mActiveWatchers = new ArrayMap<>(); final ArrayMap> mStartedWatchers = new ArrayMap<>(); final ArrayMap> 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 */ @@ -893,38 +813,14 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } /** - * Call {@link AttributedOp2#onClientDeath attributedOp.onClientDeath(clientId)}. + * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}. */ - static void onClientDeath(@NonNull AttributedOp2 attributedOp, + static void onClientDeath(@NonNull AttributedOp attributedOp, @NonNull IBinder clientId) { attributedOp.onClientDeath(clientId); } - - /** - * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces} - * so that we do not log the same operation twice between instances - */ - private void readNoteOpCallerStackTraces() { - try { - if (!mNoteOpCallerStacktracesFile.exists()) { - mNoteOpCallerStacktracesFile.createNewFile(); - return; - } - - try (Scanner read = new Scanner(mNoteOpCallerStacktracesFile)) { - read.useDelimiter("\\},"); - while (read.hasNext()) { - String jsonOps = read.next(); - mNoteOpCallerStacktraces.add(NoteOpTrace.fromJson(jsonOps)); - } - } - } catch (Exception e) { - Slog.e(TAG, "Cannot parse traces noteOps", e); - } - } - - public AppOpsServiceImpl(File storagePath, Handler handler, Context context) { + AppOpsServiceImpl(File storagePath, Handler handler, Context context) { mContext = context; for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { @@ -933,30 +829,21 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); } mAppOpsServiceInterface = - new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps); + new AppOpsCheckingServiceImpl(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 */ + /** + * Handler for work when packages are removed or updated + */ private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -1034,9 +921,9 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc String newAttributionTag = dstAttributionTags.get(attributionTag); - AttributedOp2 newAttributedOp2 = op.getOrCreateAttribution(op, + AttributedOp newAttributedOp = op.getOrCreateAttribution(op, newAttributionTag); - newAttributedOp2.add(op.mAttributions.valueAt(attributionNum)); + newAttributedOp.add(op.mAttributions.valueAt(attributionNum)); op.mAttributions.removeAt(attributionNum); scheduleFastWriteLocked(); @@ -1047,6 +934,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } }; + @Override public void systemReady() { mConstants.startMonitoring(mContext.getContentResolver()); mHistoricalRegistry.systemReady(mContext.getContentResolver()); @@ -1125,66 +1013,9 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } }, 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 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); } + @Override public void packageRemoved(int uid, String packageName) { synchronized (this) { UidState uidState = mUidStates.get(uid); @@ -1217,7 +1048,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc final int numAttributions = op.mAttributions.size(); for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) { - AttributedOp2 attributedOp = op.mAttributions.valueAt(attributionNum); + AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum); while (attributedOp.isRunning()) { attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0)); @@ -1231,9 +1062,10 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory, - mHistoricalRegistry, uid, packageName)); + mHistoricalRegistry, uid, packageName)); } + @Override public void uidRemoved(int uid) { synchronized (this) { if (mUidStates.indexOfKey(uid) >= 0) { @@ -1302,7 +1134,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc int numAttributions = op.mAttributions.size(); for (int attributionNum = 0; attributionNum < numAttributions; attributionNum++) { - AttributedOp2 attributedOp = op.mAttributions.valueAt( + AttributedOp attributedOp = op.mAttributions.valueAt( attributionNum); attributedOp.onUidStateChanged(state); @@ -1316,6 +1148,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc /** * Notify the proc state or capability has changed for a certain UID. */ + @Override public void updateUidProcState(int uid, int procState, @ActivityManager.ProcessCapability int capability) { synchronized (this) { @@ -1329,6 +1162,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } + @Override public void shutdown() { Slog.w(TAG, "Writing app ops before shutdown..."); boolean doWrite = false; @@ -1343,9 +1177,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (doWrite) { writeState(); } - if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) { - writeNoteOps(); - } mHistoricalRegistry.shutdown(); } @@ -1354,12 +1185,12 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc ArrayList resOps = null; if (ops == null) { resOps = new ArrayList<>(); - for (int j=0; j= 0) { if (resOps == null) { @@ -1448,7 +1279,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc @Override public List getOpsForPackage(int uid, String packageName, int[] ops) { - enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName); + enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName); String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); if (resolvedPackageName == null) { return Collections.emptyList(); @@ -1463,7 +1294,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (resOps == null) { return null; } - ArrayList res = new ArrayList(); + ArrayList res = new ArrayList<>(); AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( pkgOps.packageName, pkgOps.uidState.uid, resOps); res.add(resPackage); @@ -1574,7 +1405,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc final String[] chainExemptPkgArray = attributionChainExemptPackages != null ? attributionChainExemptPackages.toArray( - new String[attributionChainExemptPackages.size()]) : null; + new String[attributionChainExemptPackages.size()]) : null; // Must not hold the appops lock mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps, @@ -1671,7 +1502,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) { + @Override + public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) { if (callingPid == Process.myPid()) { return; } @@ -1690,11 +1522,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } @Override - public void setUidMode(int code, int uid, int mode) { - setUidMode(code, uid, mode, null); - } - - private void setUidMode(int code, int uid, int mode, + public void setUidMode(int code, int uid, int mode, @Nullable IAppOpsCallback permissionPolicyCallback) { if (DEBUG) { Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode) @@ -1744,8 +1572,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc /** * 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 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, @@ -1856,20 +1684,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - /** - * 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, + public void setMode(int code, int uid, @NonNull String packageName, int mode, @Nullable IAppOpsCallback permissionPolicyCallback) { enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); verifyIncomingOp(code); @@ -2045,11 +1861,11 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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, + callbacks = addCallbacks(callbacks, code, uidState.uid, + packageName, previousMode, mAppOpsServiceInterface.getOpModeChangedListeners(code)); - callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mAppOpsServiceInterface + callbacks = addCallbacks(callbacks, code, uidState.uid, + packageName, previousMode, mAppOpsServiceInterface .getPackageModeChangedListeners(packageName)); allChanges = addChange(allChanges, code, uidState.uid, @@ -2171,11 +1987,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - @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) { @@ -2239,35 +2050,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - 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, + public int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw) { verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { @@ -2284,11 +2068,10 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc /** * 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 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. - * + * @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, @@ -2323,49 +2106,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - @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); @@ -2401,117 +2141,35 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } @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) { + public int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String attributionTag, @Nullable String message) { verifyIncomingUid(uid); verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); + return AppOpsManager.MODE_ERRORED; } String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); if (resolvedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); + return AppOpsManager.MODE_IGNORED; } return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, - Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); + Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); } - private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName, + @Override + public int 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) { + @Nullable String proxyAttributionTag, @OpFlags int flags) { 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); + return AppOpsManager.MODE_ERRORED; } synchronized (this) { @@ -2520,14 +2178,15 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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); + if (DEBUG) { + Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid + + " package " + packageName + "flags: " + + AppOpsManager.flagsToString(flags)); + } + return AppOpsManager.MODE_ERRORED; } final Op op = getOpLocked(ops, code, uid, true); - final AttributedOp2 attributedOp = op.getOrCreateAttribution(op, attributionTag); + final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); if (attributedOp.isRunning()) { Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code " + code + " startTime of in progress event=" @@ -2540,34 +2199,37 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc attributedOp.rejected(uidState.getState(), flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_IGNORED); - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); + return AppOpsManager.MODE_IGNORED; } // 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)); + 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); + return uidMode; } } 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)); + 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); + return mode; } } if (DEBUG) { @@ -2583,13 +2245,21 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc uidState.getState(), flags); - if (shouldCollectAsyncNotedOp) { - collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, - shouldCollectMessage); - } + return AppOpsManager.MODE_ALLOWED; + } + } - return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, - packageName); + @Override + public boolean isAttributionTagValid(int uid, @NonNull String packageName, + @Nullable String attributionTag, + @Nullable String proxyPackageName) { + try { + return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName) + .isAttributionTagValid; + } catch (SecurityException ignored) { + // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls + // when they need the bypass object. + return false; } } @@ -2733,173 +2403,20 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - /** - * 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 key = getAsyncNotedOpsKey(packageName, uid); - - RemoteCallbackList 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 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 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 key = getAsyncNotedOpsKey(packageName, uid); - - verifyAndGetBypass(uid, packageName, null); - - synchronized (this) { - RemoteCallbackList callbacks = mAsyncOpWatchers.get(key); - if (callbacks == null) { - callbacks = new RemoteCallbackList() { - @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 key = getAsyncNotedOpsKey(packageName, uid); - - verifyAndGetBypass(uid, packageName, null); - - synchronized (this) { - RemoteCallbackList callbacks = mAsyncOpWatchers.get(key); - if (callbacks != null) { - callbacks.unregister(callback); - if (callbacks.getRegisteredCallbackCount() == 0) { - mAsyncOpWatchers.remove(key); - } - } - } - } - - @Override - public List 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, + public int startOperation(@NonNull IBinder clientId, 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) { + boolean startIfModeDefault, @NonNull String message, + @AttributionFlags int attributionFlags, int attributionChainId) { verifyIncomingUid(uid); verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); + return AppOpsManager.MODE_ERRORED; } String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); if (resolvedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); + return AppOpsManager.MODE_IGNORED; } // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution @@ -2908,120 +2425,25 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc // // 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); + int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false); if (result != AppOpsManager.MODE_ALLOWED) { - return new SyncNotedAppOp(result, code, attributionTag, packageName); + return result; } } 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); + attributionFlags, 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, + @Override + public int 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, + boolean startIfModeDefault, @AttributionFlags int attributionFlags, int attributionChainId, boolean dryRun) { PackageVerificationResult pvr; try { @@ -3031,11 +2453,10 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } catch (SecurityException e) { Slog.e(TAG, "startOperation", e); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); + return AppOpsManager.MODE_ERRORED; } - boolean isRestricted = false; + boolean isRestricted; int startType = START_TYPE_FAILED; synchronized (this) { final Ops ops = getOpsLocked(uid, packageName, attributionTag, @@ -3046,14 +2467,15 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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); + if (DEBUG) { + Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid + + " package " + packageName + " flags: " + + AppOpsManager.flagsToString(flags)); + } + return AppOpsManager.MODE_ERRORED; } final Op op = getOpLocked(ops, code, uid, true); - final AttributedOp2 attributedOp = op.getOrCreateAttribution(op, attributionTag); + final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); final UidState uidState = ops.uidState; isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false); @@ -3073,28 +2495,31 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode, startType, attributionFlags, attributionChainId); } - return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); + return uidMode; } } 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 (!shouldStartForMode(mode, startIfModeDefault)) { + 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); + return mode; } } - if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid - + " package " + packageName + " restricted: " + isRestricted - + " flags: " + AppOpsManager.flagsToString(flags)); + if (DEBUG) { + Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid + + " package " + packageName + " restricted: " + isRestricted + + " flags: " + AppOpsManager.flagsToString(flags)); + } if (!dryRun) { try { if (isRestricted) { @@ -3116,24 +2541,13 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - 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); + // Possible bug? The raw mode could have been MODE_DEFAULT to reach here. + return isRestricted ? MODE_IGNORED : MODE_ALLOWED; } @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))) { @@ -3149,55 +2563,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } @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, + public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName, String attributionTag) { PackageVerificationResult pvr; try { @@ -3218,7 +2584,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc + attributionTag + ") op=" + AppOpsManager.opToName(code)); return; } - final AttributedOp2 attributedOp = op.mAttributions.get(attributionTag); + final AttributedOp attributedOp = op.mAttributions.get(attributionTag); if (attributedOp == null) { Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "(" + attributionTag + ") op=" + AppOpsManager.opToName(code)); @@ -3234,9 +2600,9 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull - String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags - int attributionFlags, int attributionChainId) { + void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, boolean active, + @AttributionFlags int attributionFlags, int attributionChainId) { ArraySet dispatchedCallbacks = null; final int callbackListCount = mActiveWatchers.size(); for (int i = 0; i < callbackListCount; i++) { @@ -3394,52 +2760,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - @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; @@ -3498,16 +2818,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc || 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) { @@ -3521,7 +2831,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc return uidState; } - private void updateAppWidgetVisibility(SparseArray uidPackageNames, boolean visible) { + @Override + public void updateAppWidgetVisibility(SparseArray uidPackageNames, boolean visible) { synchronized (this) { getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible); } @@ -3538,18 +2849,22 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc return mPackageManagerInternal; } + @Override + public void verifyPackage(int uid, String packageName) { + verifyAndGetBypass(uid, packageName, null); + } + /** * 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); + == PackageManager.PERMISSION_GRANTED); } /** @@ -3565,13 +2880,12 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc * 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 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 + * attribution tag is valid */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, @Nullable String attributionTag, @Nullable String proxyPackageName) { @@ -3632,7 +2946,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc + "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); + + UserHandle.getAppId(uid) + otherUidMessage); } return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED, /* isAttributionTagValid */ true); @@ -3672,7 +2986,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (!mPlatformCompat.isChangeEnabledByPackageName( SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName, userId) || !mPlatformCompat.isChangeEnabledByUid( - SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, + SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, callingUid)) { // Do not override tags if overriding is not enabled for this package isAttributionTagValid = true; @@ -3718,13 +3032,13 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc /** * 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 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? - + * @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, @@ -3782,21 +3096,21 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc mWriteScheduled = true; mFastWriteScheduled = true; mHandler.removeCallbacks(mWriteRunner); - mHandler.postDelayed(mWriteRunner, 10*1000); + 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 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 - * + * @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, @@ -3875,7 +3189,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc return false; } - void readState() { + @Override + public void readState() { int oldVersion = NO_VERSION; synchronized (mFile) { synchronized (this) { @@ -3894,7 +3209,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { - ; + // Parse next until we reach the start or end } if (type != XmlPullParser.START_TAG) { @@ -4018,7 +3333,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (tagName.equals("op")) { final int code = parser.getAttributeInt(null, "n"); final int mode = parser.getAttributeInt(null, "m"); - setUidMode(code, uid, mode); + setUidMode(code, uid, mode, null); } else { Slog.w(TAG, "Unknown element under : " + parser.getName()); @@ -4075,7 +3390,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent, @Nullable String attribution) throws NumberFormatException, IOException, XmlPullParserException { - final AttributedOp2 attributedOp = parent.getOrCreateAttribution(parent, attribution); + final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution); final long key = parser.getAttributeLong(null, "n"); final int uidState = extractUidStateFromKey(key); @@ -4134,7 +3449,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc ops.put(op.op, op); } - void writeState() { + @Override + public void writeState() { synchronized (mFile) { FileOutputStream stream; try { @@ -4189,7 +3505,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (allOps != null) { String lastPkg = null; - for (int i=0; i ops = pkg.getOps(); - for (int j=0; j= 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 ] [--attribution ] " - + " "); - pw.println(" Starts a given operation for a particular application."); - pw.println(" stop [--user ] [--attribution ] " - + " "); - pw.println(" Stops a given operation for a particular application."); - pw.println(" set [--user ] <[--uid] PACKAGE | UID> "); - pw.println(" Set the mode for a particular application and operation."); - pw.println(" get [--user ] [--attribution ] " - + "[]"); - pw.println(" Return the mode for a particular application and optional operation."); - pw.println(" query-op [--user ] []"); - pw.println(" Print all packages that currently have the given op in the given mode."); - pw.println(" reset [--user ] []"); - 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(" an Android package name or its UID if prefixed by --uid"); - pw.println(" an AppOps operation."); - pw.println(" one of allow, ignore, deny, or default"); - pw.println(" 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 ops = new ArrayList<>(); - if (shell.packageName != null) { - // Uid mode overrides package mode, so make sure it's also reported - List 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 entries = packageOps.getOps(); - for (int j=0; j 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 entries = ops.get(i).getOps(); - for (int j=0; j 0 && dumpMode < 0) { @@ -5223,14 +4040,10 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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 pkgOps = uidState.pkgOps; @@ -5252,8 +4065,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } if (pkgOps != null) { for (int pkgi = 0; - (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size(); - pkgi++) { + (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size(); + pkgi++) { Ops ops = pkgOps.valueAt(pkgi); if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) { hasOp = true; @@ -5280,7 +4093,9 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":"); + pw.print(" Uid "); + UserHandle.formatUid(pw, uidState.uid); + pw.println(":"); uidState.dump(pw, nowElapsed); if (uidState.foregroundOps != null && (dumpMode < 0 || dumpMode == AppOpsManager.MODE_FOREGROUND)) { @@ -5310,8 +4125,10 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (dumpMode >= 0 && dumpMode != mode) { continue; } - pw.print(" "); pw.print(AppOpsManager.opToName(code)); - pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode)); + pw.print(" "); + pw.print(AppOpsManager.opToName(code)); + pw.print(": mode="); + pw.println(AppOpsManager.modeToName(mode)); } } @@ -5325,7 +4142,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc continue; } boolean printedPackage = false; - for (int j=0; j= 0 && dumpOp != opCode) { @@ -5335,11 +4152,15 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc continue; } if (!printedPackage) { - pw.print(" Package "); pw.print(ops.packageName); pw.println(":"); + 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())); + 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 "); @@ -5347,7 +4168,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc final Op switchObj = ops.get(switchOp); int mode = switchObj == null ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode(); - pw.print("="); pw.print(AppOpsManager.modeToName(mode)); + pw.print("="); + pw.print(AppOpsManager.modeToName(mode)); } pw.println("): "); dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now, @@ -5362,17 +4184,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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:"); @@ -5416,7 +4227,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc if (userHandle != UserHandle.getCallingUserId()) { if (mContext.checkCallingOrSelfPermission(Manifest.permission .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission + && 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 "); @@ -5446,8 +4257,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY)); mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsServiceImpl::updateStartedOpModeForUser, this, code, restricted, - userHandle)); + AppOpsServiceImpl::updateStartedOpModeForUser, this, code, + restricted, userHandle)); } if (restrictionState.isDefault()) { @@ -5457,6 +4268,64 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } + @Override + public void setGlobalRestriction(int code, boolean restricted, IBinder token) { + if (Binder.getCallingPid() != Process.myPid()) { + throw new SecurityException("Only the system can set global restrictions"); + } + + synchronized (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, this, code, UID_ANY)); + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::updateStartedOpModeForUser, 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 (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; + } + private void updateStartedOpModeForUser(int code, boolean restricted, int userId) { synchronized (AppOpsServiceImpl.this) { int numUids = mUidStates.size(); @@ -5485,7 +4354,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } int numAttrTags = op.mAttributions.size(); for (int attrNum = 0; attrNum < numAttrTags; attrNum++) { - AttributedOp2 attrOp = op.mAttributions.valueAt(attrNum); + AttributedOp attrOp = op.mAttributions.valueAt(attrNum); if (restricted && attrOp.isRunning()) { attrOp.pause(); } else if (attrOp.isPaused()) { @@ -5495,7 +4364,8 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - private void notifyWatchersOfChange(int code, int uid) { + @Override + public void notifyWatchersOfChange(int code, int uid) { final ArraySet modeChangedListenerSet; synchronized (this) { modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code); @@ -5504,7 +4374,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - notifyOpChanged(modeChangedListenerSet, code, uid, null); + notifyOpChanged(modeChangedListenerSet, code, uid, null); } @Override @@ -5563,7 +4433,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc final long identity = Binder.clearCallingIdentity(); try { final List packageOps = getOpsForPackage(proxiedUid, - proxiedPackageName, new int[] {op}); + proxiedPackageName, new int[]{op}); if (packageOps == null || packageOps.isEmpty()) { return false; } @@ -5668,258 +4538,6 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc 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 getPackageListAndResample() { - List packageNames = getPackageNamesForSampling(); - synchronized (this) { - resamplePackageAndAppOpLocked(packageNames); - } - return packageNames; - } - - /** Resamples package and appop to watch from the list provided. */ - private void resamplePackageAndAppOpLocked(@NonNull List 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 candidates) { - AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); - List 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() { - @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 getRuntimeAppOpsList() { - ArrayList 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 getPackageNamesForSampling() { - List 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) { @@ -5938,7 +4556,7 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - private static int resolveUid(String packageName) { + private static int resolveUid(String packageName) { if (packageName == null) { return Process.INVALID_UID; } @@ -5977,45 +4595,45 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } private final class ClientUserRestrictionState implements DeathRecipient { - private final IBinder token; + private final IBinder mToken; ClientUserRestrictionState(IBinder token) throws RemoteException { token.linkToDeath(this, 0); - this.token = token; + this.mToken = token; } public boolean setRestriction(int code, boolean restricted, PackageTagsList excludedPackageTags, int userId) { - return mAppOpsRestrictions.setUserRestriction(token, userId, code, + return mAppOpsRestrictions.setUserRestriction(mToken, userId, code, restricted, excludedPackageTags); } public boolean hasRestriction(int code, String packageName, String attributionTag, int userId, boolean isCheckOp) { - return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName, + return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName, attributionTag, isCheckOp); } public void removeUser(int userId) { - mAppOpsRestrictions.clearUserRestrictions(token, userId); + mAppOpsRestrictions.clearUserRestrictions(mToken, userId); } public boolean isDefault() { - return !mAppOpsRestrictions.hasUserRestrictions(token); + return !mAppOpsRestrictions.hasUserRestrictions(mToken); } @Override public void binderDied() { synchronized (AppOpsServiceImpl.this) { - mAppOpsRestrictions.clearUserRestrictions(token); - mOpUserRestrictions.remove(token); + mAppOpsRestrictions.clearUserRestrictions(mToken); + mOpUserRestrictions.remove(mToken); destroy(); } } public void destroy() { - token.unlinkToDeath(this, 0); + mToken.unlinkToDeath(this, 0); } } @@ -6052,472 +4670,10 @@ public class AppOpsServiceImpl extends IAppOpsService.Stub implements Persistenc } } - private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal { - @Override public void setDeviceAndProfileOwners(SparseIntArray owners) { - synchronized (AppOpsServiceImpl.this) { - mProfileOwners = owners; - } - } - - @Override - public void updateAppWidgetVisibility(SparseArray 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; + public void setDeviceAndProfileOwners(SparseIntArray owners) { 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; + mProfileOwners = owners; } } } diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java index 18f659e4c62a9..8420fcbd346fd 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java @@ -13,197 +13,482 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.server.appop; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.AppOpsManager.Mode; -import android.util.ArraySet; -import android.util.SparseBooleanArray; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.AttributionSource; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PackageTagsList; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.SparseArray; import android.util.SparseIntArray; +import com.android.internal.app.IAppOpsActiveCallback; +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; +import com.android.internal.app.IAppOpsStartedCallback; + +import dalvik.annotation.optimization.NeverCompile; + +import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; /** - * Interface for accessing and modifying modes for app-ops i.e. package and uid modes. - * This interface also includes functions for added and removing op mode watchers. - * In the future this interface will also include op restrictions. + * */ -public interface AppOpsServiceInterface { - /** - * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid. - * Returns an empty SparseIntArray if nothing is set. - * @param uid for which we need the app-ops and their modes. - */ - SparseIntArray getNonDefaultUidModes(int uid); +public interface AppOpsServiceInterface extends PersistenceScheduler { /** - * Returns the app-op mode for a particular app-op of a uid. - * Returns default op mode if the op mode for particular uid and op is not set. - * @param uid user id for which we need the mode. - * @param op app-op for which we need the mode. - * @return mode of the app-op. - */ - int getUidMode(int uid, int op); - - /** - * Set the app-op mode for a particular uid and op. - * The mode is not set if the mode is the same as the default mode for the op. - * @param uid user id for which we want to set the mode. - * @param op app-op for which we want to set the mode. - * @param mode mode for the app-op. - * @return true if op mode is changed. - */ - boolean setUidMode(int uid, int op, @Mode int mode); - - /** - * Gets the app-op mode for a particular package. - * Returns default op mode if the op mode for the particular package is not set. - * @param packageName package name for which we need the op mode. - * @param op app-op for which we need the mode. - * @param userId user id associated with the package. - * @return the mode of the app-op. - */ - int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId); - - /** - * Sets the app-op mode for a particular package. - * @param packageName package name for which we need to set the op mode. - * @param op app-op for which we need to set the mode. - * @param mode the mode of the app-op. - * @param userId user id associated with the package. * */ - void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId); + void systemReady(); /** - * Stop tracking any app-op modes for a package. - * @param packageName Name of the package for which we want to remove all mode tracking. - * @param userId user id associated with the package. + * */ - boolean removePackage(@NonNull String packageName, @UserIdInt int userId); + void shutdown(); /** - * Stop tracking any app-op modes for this uid. - * @param uid user id for which we want to remove all tracking. + * + * @param uid + * @param packageName */ - void removeUid(int uid); + void verifyPackage(int uid, String packageName); /** - * Returns true if all uid modes for this uid are - * in default state. - * @param uid user id + * + * @param op + * @param packageName + * @param flags + * @param callback */ - boolean areUidModesDefault(int uid); + void startWatchingModeWithFlags(int op, String packageName, int flags, + IAppOpsCallback callback); /** - * Returns true if all package modes for this package name are - * in default state. - * @param packageName package name. - * @param userId user id associated with the package. + * + * @param callback */ - boolean arePackageModesDefault(String packageName, @UserIdInt int userId); + void stopWatchingMode(IAppOpsCallback callback); /** - * Stop tracking app-op modes for all uid and packages. + * + * @param ops + * @param callback */ - void clearAllModes(); + void startWatchingActive(int[] ops, IAppOpsActiveCallback callback); /** - * Registers changedListener to listen to op's mode change. - * @param changedListener the listener that must be trigger on the op's mode change. - * @param op op representing the app-op whose mode change needs to be listened to. + * + * @param callback */ - void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); + void stopWatchingActive(IAppOpsActiveCallback callback); /** - * Registers changedListener to listen to package's app-op's mode change. - * @param changedListener the listener that must be trigger on the mode change. - * @param packageName of the package whose app-op's mode change needs to be listened to. + * + * @param ops + * @param callback */ - void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, - @NonNull String packageName); + void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback); /** - * Stop the changedListener from triggering on any mode change. - * @param changedListener the listener that needs to be removed. + * + * @param callback */ - void removeListener(@NonNull OnOpModeChangedListener changedListener); + void stopWatchingStarted(IAppOpsStartedCallback callback); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Returns a set of OnOpModeChangedListener that are listening for op's mode changes. - * @param op app-op whose mode change is being listened to. + * + * @param ops + * @param callback */ - ArraySet getOpModeChangedListeners(int op); + void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes. - * @param packageName of package whose app-op's mode change is being listened to. + * + * @param callback */ - ArraySet getPackageModeChangedListeners(@NonNull String packageName); + void stopWatchingNoted(IAppOpsNotedCallback callback); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Notify that the app-op's mode is changed by triggering the change listener. - * @param op App-op whose mode has changed - * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users) + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param startIfModeDefault + * @param message + * @param attributionFlags + * @param attributionChainId + * @return */ - void notifyWatchersOfChange(int op, int uid); + int startOperation(@NonNull IBinder clientId, int code, int uid, + @Nullable String packageName, @Nullable String attributionTag, + boolean startIfModeDefault, @NonNull String message, + @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId); + + + int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int proxyUid, String proxyPackageName, + @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags, + boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId, boolean dryRun); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Notify that the app-op's mode is changed by triggering the change listener. - * @param changedListener the change listener. - * @param op App-op whose mode has changed - * @param uid user id associated with the app-op - * @param packageName package name that is associated with the app-op + * + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag */ - void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, - @Nullable String packageName); + void finishOperation(IBinder clientId, int code, int uid, String packageName, + String attributionTag); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Notify that the app-op's mode is changed to all packages associated with the uid by - * triggering the appropriate change listener. - * @param op App-op whose mode has changed - * @param uid user id associated with the app-op - * @param onlyForeground true if only watchers that - * @param callbackToIgnore callback that should be ignored. + * + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag */ - void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, - @Nullable OnOpModeChangedListener callbackToIgnore); + void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName, + String attributionTag); /** - * TODO: Move hasForegroundWatchers and foregroundOps into this. - * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in - * foregroundOps. - * @param uid for which the app-op's mode needs to be marked. - * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. - * @return foregroundOps. + * + * @param uidPackageNames + * @param visible */ - SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps); + void updateAppWidgetVisibility(SparseArray uidPackageNames, boolean visible); /** - * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in - * foregroundOps. - * @param packageName for which the app-op's mode needs to be marked. - * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. - * @param userId user id associated with the package. - * @return foregroundOps. + * */ - SparseBooleanArray evalForegroundPackageOps(String packageName, - SparseBooleanArray foregroundOps, @UserIdInt int userId); + void readState(); /** - * Dump op mode and package mode listeners and their details. - * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an - * app-op, only the watchers for that app-op are dumped. - * @param dumpUid uid for which we want to dump op mode watchers. - * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name. - * @param printWriter writer to dump to. + * */ - boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter); + void writeState(); + + /** + * + * @param uid + * @param packageName + */ + void packageRemoved(int uid, String packageName); + + /** + * + * @param uid + */ + void uidRemoved(int uid); + + /** + * + * @param uid + * @param procState + * @param capability + */ + void updateUidProcState(int uid, int procState, + @ActivityManager.ProcessCapability int capability); + + /** + * + * @param ops + * @return + */ + List getPackagesForOps(int[] ops); + + /** + * + * @param uid + * @param packageName + * @param ops + * @return + */ + List getOpsForPackage(int uid, String packageName, + int[] ops); + + /** + * + * @param uid + * @param packageName + * @param attributionTag + * @param opNames + * @param dataType + * @param filter + * @param beginTimeMillis + * @param endTimeMillis + * @param flags + * @param callback + */ + void getHistoricalOps(int uid, String packageName, String attributionTag, + List opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback); + + /** + * + * @param uid + * @param packageName + * @param attributionTag + * @param opNames + * @param dataType + * @param filter + * @param beginTimeMillis + * @param endTimeMillis + * @param flags + * @param callback + */ + void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, + List opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback); + + /** + * + */ + void reloadNonHistoricalState(); + + /** + * + * @param uid + * @param ops + * @return + */ + List getUidOps(int uid, int[] ops); + + /** + * + * @param owners + */ + void setDeviceAndProfileOwners(SparseIntArray owners); + + // used in audio restriction calls, might just copy the logic to avoid having this call. + /** + * + * @param callingPid + * @param callingUid + * @param targetUid + */ + void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid); + + /** + * + * @param code + * @param uid + * @param mode + * @param permissionPolicyCallback + */ + void setUidMode(int code, int uid, int mode, + @Nullable IAppOpsCallback permissionPolicyCallback); + + /** + * + * @param code + * @param uid + * @param packageName + * @param mode + * @param permissionPolicyCallback + */ + void setMode(int code, int uid, @NonNull String packageName, int mode, + @Nullable IAppOpsCallback permissionPolicyCallback); + + /** + * + * @param reqUserId + * @param reqPackageName + */ + void resetAllModes(int reqUserId, String reqPackageName); + + /** + * + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param raw + * @return + */ + int checkOperation(int code, int uid, String packageName, + @Nullable String attributionTag, boolean raw); + + /** + * + * @param uid + * @param packageName + * @return + */ + int checkPackage(int uid, String packageName); + + /** + * + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param message + * @return + */ + int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String attributionTag, @Nullable String message); + + /** + * + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param proxyUid + * @param proxyPackageName + * @param proxyAttributionTag + * @param flags + * @return + */ + @AppOpsManager.Mode + int noteOperationUnchecked(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int proxyUid, String proxyPackageName, + @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags); + + boolean isAttributionTagValid(int uid, @NonNull String packageName, + @Nullable String attributionTag, @Nullable String proxyPackageName); + + /** + * + * @param fd + * @param pw + * @param args + */ + @NeverCompile + // Avoid size overhead of debugging code. + void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + /** + * + * @param restrictions + * @param token + * @param userHandle + */ + void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle); + + /** + * + * @param code + * @param restricted + * @param token + * @param userHandle + * @param excludedPackageTags + */ + void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, + PackageTagsList excludedPackageTags); + + /** + * + * @param code + * @param restricted + * @param token + */ + void setGlobalRestriction(int code, boolean restricted, IBinder token); + + /** + * + * @param code + * @param user + * @param pkg + * @param attributionTag + * @return + */ + int getOpRestrictionCount(int code, UserHandle user, String pkg, + String attributionTag); + + /** + * + * @param code + * @param uid + */ + // added to interface for audio restriction stuff + void notifyWatchersOfChange(int code, int uid); + + /** + * + * @param userHandle + * @throws RemoteException + */ + void removeUser(int userHandle) throws RemoteException; + + /** + * + * @param code + * @param uid + * @param packageName + * @return + */ + boolean isOperationActive(int code, int uid, String packageName); + + /** + * + * @param op + * @param proxyPackageName + * @param proxyAttributionTag + * @param proxiedUid + * @param proxiedPackageName + * @return + */ + // TODO this one might not need to be in the interface + boolean isProxying(int op, @NonNull String proxyPackageName, + @NonNull String proxyAttributionTag, int proxiedUid, + @NonNull String proxiedPackageName); + + /** + * + * @param packageName + */ + void resetPackageOpsNoHistory(@NonNull String packageName); + + /** + * + * @param mode + * @param baseSnapshotInterval + * @param compressionStep + */ + void setHistoryParameters(@AppOpsManager.HistoricalMode int mode, + long baseSnapshotInterval, int compressionStep); + + /** + * + * @param offsetMillis + */ + void offsetHistory(long offsetMillis); + + /** + * + * @param ops + */ + void addHistoricalOps(AppOpsManager.HistoricalOps ops); + + /** + * + */ + void resetHistoryParameters(); + + /** + * + */ + void clearHistory(); + + /** + * + * @param offlineDurationMillis + */ + void rebootHistory(long offlineDurationMillis); } diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index 5114bd59f0842..c1434e4d9f4db 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -59,7 +59,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private final DelayableExecutor mExecutor; private final Clock mClock; private ActivityManagerInternal mActivityManagerInternal; - private AppOpsService.Constants mConstants; + private AppOpsServiceImpl.Constants mConstants; private SparseIntArray mUidStates = new SparseIntArray(); private SparseIntArray mPendingUidStates = new SparseIntArray(); @@ -85,7 +85,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, Handler handler, Executor lockingExecutor, Clock clock, - AppOpsService.Constants constants) { + AppOpsServiceImpl.Constants constants) { this(activityManagerInternal, new DelayableExecutor() { @Override @@ -102,7 +102,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { @VisibleForTesting AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, - DelayableExecutor executor, Clock clock, AppOpsService.Constants constants, + DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants, Thread executorThread) { mActivityManagerInternal = activityManagerInternal; mExecutor = executor; diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java deleted file mode 100644 index 96048f3b7ed0a..0000000000000 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl2.java +++ /dev/null @@ -1,625 +0,0 @@ -/* - * 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 - 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 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/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index dcc36bcf6149c..7970269086190 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -40,9 +40,9 @@ import java.util.List; import java.util.NoSuchElementException; final class AttributedOp { - private final @NonNull AppOpsService mAppOpsService; + private final @NonNull AppOpsServiceImpl mAppOpsService; public final @Nullable String tag; - public final @NonNull AppOpsService.Op parent; + public final @NonNull AppOpsServiceImpl.Op parent; /** * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination @@ -80,8 +80,8 @@ final class AttributedOp { // @GuardedBy("mAppOpsService") @Nullable ArrayMap mPausedInProgressEvents; - AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag, - @NonNull AppOpsService.Op parent) { + AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag, + @NonNull AppOpsServiceImpl.Op parent) { mAppOpsService = appOpsService; this.tag = tag; this.parent = parent; @@ -131,8 +131,8 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { - proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag); + proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, + proxyPackageName, proxyAttributionTag); } AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key); @@ -238,7 +238,7 @@ final class AttributedOp { if (event == null) { event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, SystemClock.elapsedRealtime(), clientId, tag, - PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), + PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId), proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, attributionFlags, attributionChainId); events.put(clientId, event); @@ -251,9 +251,9 @@ final class AttributedOp { event.mNumUnfinishedStarts++; if (isStarted) { - mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, uidState, flags, startTime, attributionFlags, - attributionChainId); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, + parent.uid, parent.packageName, tag, uidState, flags, startTime, + attributionFlags, attributionChainId); } } @@ -309,8 +309,8 @@ final class AttributedOp { mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), finishedEvent); - mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, - parent.packageName, tag, event.getUidState(), + mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, + parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), event.getAttributionFlags(), event.getAttributionChainId()); @@ -334,13 +334,13 @@ final class AttributedOp { @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) { if (!isPaused()) { - Slog.wtf(AppOpsService.TAG, "No ops running or paused"); + Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused"); return; } int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId); if (indexOfToken < 0) { - Slog.wtf(AppOpsService.TAG, "No op running or paused for the client"); + Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client"); return; } else if (isPausing) { // already paused @@ -416,9 +416,9 @@ final class AttributedOp { mInProgressEvents.put(event.getClientId(), event); event.setStartElapsedTime(SystemClock.elapsedRealtime()); event.setStartTime(startTime); - mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.getUidState(), event.getFlags(), startTime, - event.getAttributionFlags(), event.getAttributionChainId()); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, + parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(), + startTime, event.getAttributionFlags(), event.getAttributionChainId()); if (shouldSendActive) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, true, event.getAttributionFlags(), @@ -503,8 +503,8 @@ final class AttributedOp { newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1; } } catch (RemoteException e) { - if (AppOpsService.DEBUG) { - Slog.e(AppOpsService.TAG, + if (AppOpsServiceImpl.DEBUG) { + Slog.e(AppOpsServiceImpl.TAG, "Cannot switch to new uidState " + newState); } } @@ -555,8 +555,8 @@ final class AttributedOp { ArrayMap ignoredEvents = opToAdd.isRunning() ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents; - Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: " - + opToAdd.isRunning()); + Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size() + + " app-ops, running: " + opToAdd.isRunning()); int numInProgressEvents = ignoredEvents.size(); for (int i = 0; i < numInProgressEvents; i++) { @@ -668,16 +668,22 @@ final class AttributedOp { /** * 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 startTime The time {@link AppOpCheckingServiceInterface#startOperation} + * was called + * @param startElapsedTime The elapsed time whe + * {@link AppOpCheckingServiceInterface#startOperation} was called + * @param clientId The client id of the caller of + * {@link AppOpCheckingServiceInterface#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 uidState The uidstate of the app + * {@link AppOpCheckingServiceInterface#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 proxy The proxy information, if + * {@link AppOpCheckingServiceInterface#startProxyOperation} was + * called * @param flags The trusted/nontrusted/self flags. * @throws RemoteException If the client is dying */ @@ -718,15 +724,21 @@ final class AttributedOp { /** * 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 startTime The time {@link AppOpCheckingServiceInterface#startOperation} + * was called + * @param startElapsedTime The elapsed time when + * {@link AppOpCheckingServiceInterface#startOperation} was called + * @param clientId The client id of the caller of + * {@link AppOpCheckingServiceInterface#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 uidState The uidstate of the app + * {@link AppOpCheckingServiceInterface#startOperation} was called + * for * @param flags The flags relating to the proxy - * @param proxy The proxy information, if {@link #startProxyOperation} - * was called + * @param proxy The proxy information, if + * {@link AppOpCheckingServiceInterface#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 diff --git a/services/core/java/com/android/server/appop/AttributedOp2.java b/services/core/java/com/android/server/appop/AttributedOp2.java deleted file mode 100644 index 16fa7e185f94c..0000000000000 --- a/services/core/java/com/android/server/appop/AttributedOp2.java +++ /dev/null @@ -1,870 +0,0 @@ -/* - * 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 - * - *

Key is {@link AppOpsManager#makeKey} - */ - // TODO(b/248108338) - // @GuardedBy("mAppOpsCheckingServiceImpl") - private @Nullable LongSparseArray mAccessEvents; - - /** - * Last rejected accesses for each uidState/opFlag combination - * - *

Key is {@link AppOpsManager#makeKey} - */ - // TODO(b/248108338) - // @GuardedBy("mAppOpsCheckingServiceImpl") - private @Nullable LongSparseArray mRejectEvents; - - /** - * Currently in progress startOp events - * - *

Key is clientId - */ - // TODO(b/248108338) - // @GuardedBy("mAppOpsCheckingServiceImpl") - @Nullable ArrayMap mInProgressEvents; - - /** - * Currently paused startOp events - * - *

Key is clientId - */ - // TODO(b/248108338) - // @GuardedBy("mAppOpsCheckingServiceImpl") - @Nullable ArrayMap 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 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 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 events = - isRunning ? mInProgressEvents : mPausedInProgressEvents; - - int numInProgressEvents = events.size(); - List 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 add( - @Nullable LongSparseArray a, - @Nullable LongSparseArray 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. - * - *

If there is an event for the same key in both the later event is retained. - *

{@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 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 deepClone( - @Nullable LongSparseArray original) { - if (original == null) { - return original; - } - - int size = original.size(); - LongSparseArray 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 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 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 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 { - 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 { - 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); - } - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java index 5dc12510368c5..be13bad70e16f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java @@ -48,7 +48,7 @@ public class AppOpsLegacyRestrictionsTest { StaticMockitoSession mSession; @Mock - AppOpsService.Constants mConstants; + AppOpsServiceImpl.Constants mConstants; @Mock Context mContext; @@ -57,7 +57,7 @@ public class AppOpsLegacyRestrictionsTest { Handler mHandler; @Mock - AppOpsServiceInterface mLegacyAppOpsService; + AppOpsCheckingServiceInterface mLegacyAppOpsService; AppOpsRestrictions mAppOpsRestrictions; diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index c0688d131610d..7d4bc6f47ad4d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -22,6 +22,8 @@ import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_READ_SMS; import static android.app.AppOpsManager.OP_WIFI_SCAN; import static android.app.AppOpsManager.OP_WRITE_SMS; +import static android.app.AppOpsManager.resolvePackageName; +import static android.os.Process.INVALID_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -39,6 +41,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import android.app.AppOpsManager; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.PackageOps; import android.content.ContentResolver; @@ -86,13 +89,13 @@ public class AppOpsServiceTest { private File mAppOpsFile; private Handler mHandler; - private AppOpsService mAppOpsService; + private AppOpsServiceImpl mAppOpsService; private int mMyUid; private long mTestStartMillis; private StaticMockitoSession mMockingSession; private void setupAppOpsService() { - mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext)); + mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext)); mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver()); // Always approve all permission checks @@ -161,17 +164,20 @@ public class AppOpsServiceTest { @Test public void testNoteOperationAndGetOpsForPackage() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null); // Note an op that's allowed. - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); List loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); // Note another op that's not allowed. - mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null, - false); + mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED); @@ -185,18 +191,20 @@ public class AppOpsServiceTest { @Test public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() { // This op controls WIFI_SCAN - mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED); + mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null); - assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false, - null, false).getOpMode()).isEqualTo(MODE_ALLOWED); + assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED); assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1, MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */); // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well. - mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED); - assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false, - null, false).getOpMode()).isEqualTo(MODE_ERRORED); + mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null); + assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED); assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis, MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */); @@ -205,11 +213,14 @@ public class AppOpsServiceTest { // Tests the dumping and restoring of the in-memory state to/from XML. @Test public void testStatePersistence() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); - mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null, - false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); + mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); mAppOpsService.writeState(); // Create a new app ops service which will initialize its state from XML. @@ -224,8 +235,10 @@ public class AppOpsServiceTest { // Tests that ops are persisted during shutdown. @Test public void testShutdown() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); mAppOpsService.shutdown(); // Create a new app ops service which will initialize its state from XML. @@ -238,8 +251,10 @@ public class AppOpsServiceTest { @Test public void testGetOpsForPackage() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); // Query all ops List loggedOps = mAppOpsService.getOpsForPackage( @@ -267,8 +282,10 @@ public class AppOpsServiceTest { @Test public void testPackageRemoved() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); List loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); @@ -322,8 +339,10 @@ public class AppOpsServiceTest { @Test public void testUidRemoved() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); List loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java index 98e895a86f9e5..3efd5e701013c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -76,7 +76,7 @@ public class AppOpsUidStateTrackerTest { ActivityManagerInternal mAmi; @Mock - AppOpsService.Constants mConstants; + AppOpsServiceImpl.Constants mConstants; AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor(); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index e08a715fbfe11..298dbf47d86d3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -93,12 +93,13 @@ public class AppOpsUpgradeTest { } } - private void assertSameModes(SparseArray uidStates, int op1, int op2) { + private void assertSameModes(SparseArray uidStates, + int op1, int op2) { int numberOfNonDefaultOps = 0; final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1); final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2); for(int i = 0; i < uidStates.size(); i++) { - final AppOpsService.UidState uidState = uidStates.valueAt(i); + final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i); SparseIntArray opModes = uidState.getNonDefaultUidModes(); if (opModes != null) { final int uidMode1 = opModes.get(op1, defaultModeOp1); @@ -112,12 +113,12 @@ public class AppOpsUpgradeTest { continue; } for (int j = 0; j < uidState.pkgOps.size(); j++) { - final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j); + final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j); if (ops == null) { continue; } - final AppOpsService.Op _op1 = ops.get(op1); - final AppOpsService.Op _op2 = ops.get(op2); + final AppOpsServiceImpl.Op _op1 = ops.get(op1); + final AppOpsServiceImpl.Op _op2 = ops.get(op2); final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode(); final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode(); assertEquals(mode1, mode2); @@ -158,8 +159,8 @@ public class AppOpsUpgradeTest { // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat when(testPM.getPackagesForUid(anyInt())).thenReturn(null); - AppOpsService testService = spy( - new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade + AppOpsServiceImpl testService = spy( + new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND, AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); mHandler.removeCallbacks(testService.mWriteRunner);