Prepare AttributionSource to expose to native

Separate the internal state of AttributionSource from the
class to make it a simple AIDL we can translate automatically
to native - keeping Java and native parts in sync. This
would allow writing a thin native lib for checking attribution
source permissions which would be used to teach camera and
audio about attributions.

Deinfe an AIDL interface for passing around an attribution
source and opr performing permission checker oprations allowing
native and Java permission checks on attribution chains to be
handled. The Java side permission checker functions are in a dedicated
permisison checker service on top of which sits the PermissionChecker.
We expose similar PermissionChecker native APIs sitting on top
of the same remote interface. The nice thing is that we have
native and Java permisison checkers in sync sharing remoting
code and being close in shape.

For now the PermissionChecker in Java is divorced from the
PermissionManager but in T we will consider how to unify them,
either by an extension object on the PermmissionManager or
APIs on the PermissionManager, or another approach, and then
migrate clients off the PermissionChecker APIs.

Sync app ops were not tracked across multiple binder calls which
prevents moving the permission checks in the system server as
this adds one more hop. Now sync ops are propagated backed the
call stack and only the ops for the package are dispatched to
it and the rest are propagated back to the caller, recursively.

bug: 158792096

Test: atest CtsPermission5TestCases
      atest CtsAppOps2TestCases
      atest CtsPermissionTestCases
      atest CtsPermission2TestCases
      atest CtsPermission3TestCases
      atest CtsPermission4TestCases
      atest CtsPermission5TestCases

Change-Id: Ia5cbd2eb20a2da172a5960afdddd7e467f4bcb0d
diff --git a/Android.bp b/Android.bp
index 685c69d..8db5589 100644
--- a/Android.bp
+++ b/Android.bp
@@ -325,6 +325,7 @@
         "tv_tuner_resource_manager_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
         "modules-utils-os",
+        "framework-permission-aidl-java",
     ],
 }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 35767b3..8fd9e1e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -219,6 +219,7 @@
   public class AppOpsManager {
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory();
+    method public static void collectNotedOpSync(@NonNull android.app.SyncNotedAppOp);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method public static int getNumOps();
     method public boolean isOperationActive(int, int, String);
@@ -360,6 +361,10 @@
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
   }
 
+  public final class SyncNotedAppOp implements android.os.Parcelable {
+    ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
+  }
+
   public class TaskInfo {
     method public boolean containsLaunchCookie(@NonNull android.os.IBinder);
     method @NonNull public android.content.res.Configuration getConfiguration();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index efeef1b..36d161d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2783,14 +2783,24 @@
     private static final ThreadLocal<Integer> sBinderThreadCallingUid = new ThreadLocal<>();
 
     /**
-     * If a thread is currently executing a two-way binder transaction, this stores the op-codes of
-     * the app-ops that were noted during this transaction.
+     * Optimization: we need to propagate to IPCs whether the current thread is collecting
+     * app ops but using only the thread local above is too slow as it requires a map lookup
+     * on every IPC. We add this static var that is lockless and stores an OR-ed mask of the
+     * thread id's currently collecting ops, thus reducing the map lookup to a simple bit
+     * operation except the extremely unlikely case when threads with overlapping id bits
+     * execute op collecting ops.
+     */
+    private static volatile long sThreadsListeningForOpNotedInBinderTransaction = 0L;
+
+    /**
+     * If a thread is currently executing a two-way binder transaction, this stores the
+     * ops that were noted blaming any app (the caller, the caller of the caller, etc).
      *
      * @see #getNotedOpCollectionMode
      * @see #collectNotedOpSync
      */
-    private static final ThreadLocal<ArrayMap<String, long[]>> sAppOpsNotedInThisBinderTransaction =
-            new ThreadLocal<>();
+    private static final ThreadLocal<ArrayMap<String, ArrayMap<String, long[]>>>
+            sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>();
 
     /** Whether noting for an appop should be collected */
     private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
@@ -8105,7 +8115,7 @@
             SyncNotedAppOp syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
                     collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
 
-            if (syncOp.getOpMode()== MODE_ALLOWED) {
+            if (syncOp.getOpMode() == MODE_ALLOWED) {
                 if (collectionMode == COLLECT_SELF) {
                     collectNotedOpForSelf(syncOp);
                 } else if (collectionMode == COLLECT_SYNC) {
@@ -8872,70 +8882,11 @@
      * @hide
      */
     public static void startNotedAppOpsCollection(int callingUid) {
+        sThreadsListeningForOpNotedInBinderTransaction |= Thread.currentThread().getId();
         sBinderThreadCallingUid.set(callingUid);
     }
 
     /**
-     * State of a temporarily paused noted app-ops collection.
-     *
-     * @see #pauseNotedAppOpsCollection()
-     *
-     * @hide
-     */
-    public static class PausedNotedAppOpsCollection {
-        final int mUid;
-        final @Nullable ArrayMap<String, long[]> mCollectedNotedAppOps;
-
-        PausedNotedAppOpsCollection(int uid, @Nullable ArrayMap<String,
-                long[]> collectedNotedAppOps) {
-            mUid = uid;
-            mCollectedNotedAppOps = collectedNotedAppOps;
-        }
-    }
-
-    /**
-     * Temporarily suspend collection of noted app-ops when binder-thread calls into the other
-     * process. During such a call there might be call-backs coming back on the same thread which
-     * should not be accounted to the current collection.
-     *
-     * @return a state needed to resume the collection
-     *
-     * @hide
-     */
-    public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
-        Integer previousUid = sBinderThreadCallingUid.get();
-        if (previousUid != null) {
-            ArrayMap<String, long[]> previousCollectedNotedAppOps =
-                    sAppOpsNotedInThisBinderTransaction.get();
-
-            sBinderThreadCallingUid.remove();
-            sAppOpsNotedInThisBinderTransaction.remove();
-
-            return new PausedNotedAppOpsCollection(previousUid, previousCollectedNotedAppOps);
-        }
-
-        return null;
-    }
-
-    /**
-     * Resume a collection paused via {@link #pauseNotedAppOpsCollection}.
-     *
-     * @param prevCollection The state of the previous collection
-     *
-     * @hide
-     */
-    public static void resumeNotedAppOpsCollection(
-            @Nullable PausedNotedAppOpsCollection prevCollection) {
-        if (prevCollection != null) {
-            sBinderThreadCallingUid.set(prevCollection.mUid);
-
-            if (prevCollection.mCollectedNotedAppOps != null) {
-                sAppOpsNotedInThisBinderTransaction.set(prevCollection.mCollectedNotedAppOps);
-            }
-        }
-    }
-
-    /**
      * Finish collection of noted appops on this thread.
      *
      * <p>Called at the end of a two way binder transaction.
@@ -8946,6 +8897,7 @@
      */
     public static void finishNotedAppOpsCollection() {
         sBinderThreadCallingUid.remove();
+        sThreadsListeningForOpNotedInBinderTransaction &= ~Thread.currentThread().getId();
         sAppOpsNotedInThisBinderTransaction.remove();
     }
 
@@ -8970,28 +8922,52 @@
      * <p> Delivered to caller via {@link #prefixParcelWithAppOpsIfNeeded}
      *
      * @param syncOp the op and attribution tag to note for
+     *
+     * @hide
      */
-    private void collectNotedOpSync(@NonNull SyncNotedAppOp syncOp) {
+    @TestApi
+    public static void collectNotedOpSync(@NonNull SyncNotedAppOp syncOp) {
+        collectNotedOpSync(sOpStrToOp.get(syncOp.getOp()), syncOp.getAttributionTag(),
+                syncOp.getPackageName());
+    }
+
+    /**
+     * Collect a noted op when inside of a two-way binder call.
+     *
+     * <p> Delivered to caller via {@link #prefixParcelWithAppOpsIfNeeded}
+     *
+     * @param code the op code to note for
+     * @param attributionTag the attribution tag to note for
+     * @param packageName the package to note for
+     */
+    private static void collectNotedOpSync(int code, @Nullable String attributionTag,
+            @NonNull String packageName) {
         // If this is inside of a two-way binder call:
         // We are inside of a two-way binder call. Delivered to caller via
         // {@link #prefixParcelWithAppOpsIfNeeded}
-        int op = sOpStrToOp.get(syncOp.getOp());
-        ArrayMap<String, long[]> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
+        ArrayMap<String, ArrayMap<String, long[]>> appOpsNoted =
+                sAppOpsNotedInThisBinderTransaction.get();
         if (appOpsNoted == null) {
             appOpsNoted = new ArrayMap<>(1);
             sAppOpsNotedInThisBinderTransaction.set(appOpsNoted);
         }
 
-        long[] appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
-        if (appOpsNotedForAttribution == null) {
-            appOpsNotedForAttribution = new long[2];
-            appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution);
+        ArrayMap<String, long[]> packageAppOpsNotedForAttribution = appOpsNoted.get(packageName);
+        if (packageAppOpsNotedForAttribution == null) {
+            packageAppOpsNotedForAttribution = new ArrayMap<>(1);
+            appOpsNoted.put(packageName, packageAppOpsNotedForAttribution);
         }
 
-        if (op < 64) {
-            appOpsNotedForAttribution[0] |= 1L << op;
+        long[] appOpsNotedForAttribution = packageAppOpsNotedForAttribution.get(attributionTag);
+        if (appOpsNotedForAttribution == null) {
+            appOpsNotedForAttribution = new long[2];
+            packageAppOpsNotedForAttribution.put(attributionTag, appOpsNotedForAttribution);
+        }
+
+        if (code < 64) {
+            appOpsNotedForAttribution[0] |= 1L << code;
         } else {
-            appOpsNotedForAttribution[1] |= 1L << (op - 64);
+            appOpsNotedForAttribution[1] |= 1L << (code - 64);
         }
     }
 
@@ -9045,9 +9021,7 @@
             }
         }
 
-        Integer binderUid = sBinderThreadCallingUid.get();
-
-        if (binderUid != null && binderUid == uid) {
+        if (isListeningForOpNotedInBinderTransaction()) {
             return COLLECT_SYNC;
         } else {
             return COLLECT_ASYNC;
@@ -9064,21 +9038,31 @@
      *
      * @hide
      */
+    // TODO (b/186872903) Refactor how sync noted ops are propagaged.
     public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
-        ArrayMap<String, long[]> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+        final ArrayMap<String, ArrayMap<String, long[]>> notedAppOps =
+                sAppOpsNotedInThisBinderTransaction.get();
         if (notedAppOps == null) {
             return;
         }
 
         p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
 
-        int numAttributionWithNotesAppOps = notedAppOps.size();
-        p.writeInt(numAttributionWithNotesAppOps);
+        final int packageCount = notedAppOps.size();
+        p.writeInt(packageCount);
 
-        for (int i = 0; i < numAttributionWithNotesAppOps; i++) {
+        for (int i = 0; i < packageCount; i++) {
             p.writeString(notedAppOps.keyAt(i));
-            p.writeLong(notedAppOps.valueAt(i)[0]);
-            p.writeLong(notedAppOps.valueAt(i)[1]);
+
+            final ArrayMap<String, long[]> notedTagAppOps = notedAppOps.valueAt(i);
+            final int tagCount = notedTagAppOps.size();
+            p.writeInt(tagCount);
+
+            for (int j = 0; j < tagCount; j++) {
+                p.writeString(notedTagAppOps.keyAt(j));
+                p.writeLong(notedTagAppOps.valueAt(j)[0]);
+                p.writeLong(notedTagAppOps.valueAt(j)[1]);
+            }
         }
     }
 
@@ -9093,36 +9077,57 @@
      * @hide
      */
     public static void readAndLogNotedAppops(@NonNull Parcel p) {
-        int numAttributionsWithNotedAppOps = p.readInt();
+        final int packageCount = p.readInt();
+        if (packageCount <= 0) {
+            return;
+        }
 
-        for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
-            String attributionTag = p.readString();
-            long[] rawNotedAppOps = new long[2];
-            rawNotedAppOps[0] = p.readLong();
-            rawNotedAppOps[1] = p.readLong();
+        final String myPackageName = ActivityThread.currentPackageName();
+        if (myPackageName == null) {
+            return;
+        }
 
-            if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
-                BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+        synchronized (sLock) {
+            for (int i = 0; i < packageCount; i++) {
+                final String packageName = p.readString();
 
-                synchronized (sLock) {
+                final int tagCount = p.readInt();
+                for (int j = 0; j < tagCount; j++) {
+                    final String attributionTag = p.readString();
+                    final long[] rawNotedAppOps = new long[2];
+                    rawNotedAppOps[0] = p.readLong();
+                    rawNotedAppOps[1] = p.readLong();
+
+                    if (rawNotedAppOps[0] == 0 && rawNotedAppOps[1] == 0) {
+                        continue;
+                    }
+
+                    final BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
                     for (int code = notedAppOps.nextSetBit(0); code != -1;
                             code = notedAppOps.nextSetBit(code + 1)) {
-                        if (sOnOpNotedCallback != null) {
-                            sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code, attributionTag));
-                        } else {
-                            String message = getFormattedStackTrace();
-                            sUnforwardedOps.add(
-                                    new AsyncNotedAppOp(code, Process.myUid(), attributionTag,
-                                            message, System.currentTimeMillis()));
-                            if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) {
-                                sUnforwardedOps.remove(0);
+                        if (myPackageName.equals(packageName)) {
+                            if (sOnOpNotedCallback != null) {
+                                sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code,
+                                        attributionTag, packageName));
+                            } else {
+                                String message = getFormattedStackTrace();
+                                sUnforwardedOps.add(new AsyncNotedAppOp(code, Process.myUid(),
+                                        attributionTag, message, System.currentTimeMillis()));
+                                if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) {
+                                    sUnforwardedOps.remove(0);
+                                }
                             }
+                        } else if (isListeningForOpNotedInBinderTransaction()) {
+                            collectNotedOpSync(code, attributionTag, packageName);
                         }
                     }
-                }
-                for (int code = notedAppOps.nextSetBit(0); code != -1;
-                        code = notedAppOps.nextSetBit(code + 1)) {
-                    sMessageCollector.onNoted(new SyncNotedAppOp(code, attributionTag));
+                    for (int code = notedAppOps.nextSetBit(0); code != -1;
+                            code = notedAppOps.nextSetBit(code + 1)) {
+                        if (myPackageName.equals(packageName)) {
+                            sMessageCollector.onNoted(new SyncNotedAppOp(code,
+                                    attributionTag, packageName));
+                        }
+                    }
                 }
             }
         }
@@ -9229,7 +9234,17 @@
      * @hide
      */
     public static boolean isListeningForOpNoted() {
-        return sOnOpNotedCallback != null || isCollectingStackTraces();
+        return sOnOpNotedCallback != null || isListeningForOpNotedInBinderTransaction()
+                || isCollectingStackTraces();
+    }
+
+    /**
+     * @return whether we are in a binder transaction and collecting appops.
+     */
+    private static boolean isListeningForOpNotedInBinderTransaction() {
+        return (sThreadsListeningForOpNotedInBinderTransaction
+                        & Thread.currentThread().getId()) != 0
+                && sBinderThreadCallingUid.get() != null;
     }
 
     /**
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index bc4e543..32d889e 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -19,7 +19,9 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Parcelable;
+import android.os.Process;
 
 import com.android.internal.annotations.Immutable;
 import com.android.internal.util.DataClass;
@@ -48,13 +50,19 @@
     private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
     /** attributionTag of synchronous appop noted */
     private final @Nullable String mAttributionTag;
+    /**
+     * The package this op applies to
+     * @hide
+     */
+    private final @NonNull String mPackageName;
 
     /**
      * Native code relies on parcel ordering, do not change
      * @hide
      */
+    @TestApi
     public SyncNotedAppOp(int opMode, @IntRange(from = 0L) int opCode,
-            @Nullable String attributionTag) {
+            @Nullable String attributionTag, @NonNull String packageName) {
         this.mOpCode = opCode;
         com.android.internal.util.AnnotationValidations.validate(
                 IntRange.class, null, mOpCode,
@@ -62,6 +70,7 @@
                 "to", AppOpsManager._NUM_OP - 1);
         this.mAttributionTag = attributionTag;
         this.mOpMode = opMode;
+        this.mPackageName = packageName;
     }
 
     /**
@@ -73,7 +82,25 @@
      *   attributionTag of synchronous appop noted
      */
     public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag) {
-        this(AppOpsManager.MODE_IGNORED, opCode, attributionTag);
+        this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, ActivityThread
+                .currentPackageName());
+    }
+
+    /**
+     * Creates a new SyncNotedAppOp.
+     *
+     * @param opCode
+     *   op code of synchronous appop noted
+     * @param attributionTag
+     *   attributionTag of synchronous appop noted
+     * @param packageName
+     *   The package this op applies to
+     *
+     * @hide
+     */
+    public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag,
+            @NonNull String packageName) {
+        this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, packageName);
     }
 
     /**
@@ -113,6 +140,16 @@
         return mAttributionTag;
     }
 
+    /**
+     * The package this op applies to
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
     @Override
     @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
@@ -128,7 +165,8 @@
         return true
                 && mOpMode == that.mOpMode
                 && mOpCode == that.mOpCode
-                && java.util.Objects.equals(mAttributionTag, that.mAttributionTag);
+                && java.util.Objects.equals(mAttributionTag, that.mAttributionTag)
+                && java.util.Objects.equals(mPackageName, that.mPackageName);
     }
 
     @Override
@@ -141,6 +179,7 @@
         _hash = 31 * _hash + mOpMode;
         _hash = 31 * _hash + mOpCode;
         _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
         return _hash;
     }
 
@@ -156,6 +195,7 @@
         dest.writeInt(mOpMode);
         dest.writeInt(mOpCode);
         if (mAttributionTag != null) dest.writeString(mAttributionTag);
+        dest.writeString(mPackageName);
     }
 
     @Override
@@ -173,6 +213,7 @@
         int opMode = in.readInt();
         int opCode = in.readInt();
         String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
+        String packageName = in.readString();
 
         this.mOpMode = opMode;
         this.mOpCode = opCode;
@@ -181,6 +222,9 @@
                 "from", 0L,
                 "to", AppOpsManager._NUM_OP - 1);
         this.mAttributionTag = attributionTag;
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -200,10 +244,10 @@
     };
 
     @DataClass.Generated(
-            time = 1617317997768L,
+            time = 1619711733947L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
-            inputSignatures = "private final  int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\npublic @android.annotation.NonNull java.lang.String getOp()\npublic  int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)")
+            inputSignatures = "private final  int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic  int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 2c155d58..7ab731f 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -31,10 +31,9 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.Immutable;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
 
@@ -70,10 +69,10 @@
  * This is supported to handle cases where you don't have access to the caller's attribution
  * source and you can directly use the {@link AttributionSource.Builder} APIs. However,
  * if the data flows through more than two apps (more than you access the data for the
- * caller - which you cannot know ahead of time) you need to have a handle to the {@link
- * AttributionSource} for the calling app's context in order to create an attribution context.
- * This means you either need to have an API for the other app to send you its attribution
- * source or use a platform API that pipes the callers attribution source.
+ * caller) you need to have a handle to the {@link AttributionSource} for the calling app's
+ * context in order to create an attribution context. This means you either need to have an
+ * API for the other app to send you its attribution source or use a platform API that pipes
+ * the callers attribution source.
  * <p>
  * You cannot forge an attribution chain without the participation of every app in the
  * attribution chain (aside of the special case mentioned above). To create an attribution
@@ -85,80 +84,11 @@
  * permission protected APIs since some app in the chain may not have the permission.
  */
 @Immutable
-// TODO: Codegen doesn't properly verify the class if the parcelling is inner class
-// TODO: Codegen doesn't allow overriding the constructor to change its visibility
-// TODO: Codegen applies method level annotations to argument vs the generated member (@SystemApi)
-// TODO: Codegen doesn't properly read/write IBinder members
-// TODO: Codegen doesn't properly handle Set arguments
-// TODO: Codegen requires @SystemApi annotations on fields which breaks
-//      android.signature.cts.api.AnnotationTest (need to update the test)
-// @DataClass(genEqualsHashCode = true, genConstructor = false, genBuilder = true)
 public final class AttributionSource implements Parcelable {
-    /**
-     * @hide
-     */
-    static class RenouncedPermissionsParcelling implements Parcelling<Set<String>> {
+    private final @NonNull AttributionSourceState mAttributionSourceState;
 
-        @Override
-        public void parcel(Set<String> item, Parcel dest, int parcelFlags) {
-            if (item == null) {
-                dest.writeInt(-1);
-            } else {
-                dest.writeInt(item.size());
-                for (String permission : item) {
-                    dest.writeString8(permission);
-                }
-            }
-        }
-
-        @Override
-        public Set<String> unparcel(Parcel source) {
-            final int size = source.readInt();
-            if (size < 0) {
-                return null;
-            }
-            final ArraySet<String> result = new ArraySet<>(size);
-            for (int i = 0; i < size; i++) {
-                result.add(source.readString8());
-            }
-            return result;
-        }
-    }
-
-    /**
-     * The UID that is accessing the permission protected data.
-     */
-    private final int mUid;
-
-    /**
-     * The package that is accessing the permission protected data.
-     */
-    private @Nullable String mPackageName = null;
-
-    /**
-     * The attribution tag of the app accessing the permission protected data.
-     */
-    private @Nullable String mAttributionTag = null;
-
-    /**
-     * Unique token for that source.
-     *
-     * @hide
-     */
-    private @Nullable IBinder mToken = null;
-
-    /**
-     * Permissions that should be considered revoked regardless if granted.
-     *
-     * @hide
-     */
-    @DataClass.ParcelWith(RenouncedPermissionsParcelling.class)
-    private @Nullable Set<String> mRenouncedPermissions = null;
-
-    /**
-     * The next app to receive the permission protected data.
-     */
-    private @Nullable AttributionSource mNext = null;
+    private @Nullable AttributionSource mNextCached;
+    private @Nullable Set<String> mRenouncedPermissionsCached;
 
     /** @hide */
     @TestApi
@@ -171,8 +101,7 @@
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, /*token*/ null,
-                /*renouncedPermissions*/ null, next);
+        this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next);
     }
 
     /** @hide */
@@ -180,8 +109,8 @@
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
             @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, /*token*/ null,
-                renouncedPermissions, next);
+        this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null)
+                ? renouncedPermissions.toArray(new String[0]) : null, next);
     }
 
     /** @hide */
@@ -191,16 +120,49 @@
                 /*token*/ null, /*renouncedPermissions*/ null, next);
     }
 
+    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+            @Nullable IBinder token, @Nullable String[] renouncedPermissions,
+            @Nullable AttributionSource next) {
+        mAttributionSourceState = new AttributionSourceState();
+        mAttributionSourceState.uid = uid;
+        mAttributionSourceState.packageName = packageName;
+        mAttributionSourceState.attributionTag = attributionTag;
+        mAttributionSourceState.token = token;
+        mAttributionSourceState.renouncedPermissions = renouncedPermissions;
+        mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
+                {next.mAttributionSourceState} : null;
+    }
+
+    AttributionSource(@NonNull Parcel in) {
+        this(AttributionSourceState.CREATOR.createFromParcel(in));
+    }
+
+    /** @hide */
+    public AttributionSource(@NonNull AttributionSourceState attributionSourceState) {
+        mAttributionSourceState = attributionSourceState;
+    }
+
     /** @hide */
     public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
-        return new AttributionSource(mUid, mPackageName, mAttributionTag,  mToken,
-                mRenouncedPermissions, next);
+        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+                getToken(), mAttributionSourceState.renouncedPermissions, next);
     }
 
     /** @hide */
     public AttributionSource withToken(@Nullable IBinder token) {
-        return new AttributionSource(mUid, mPackageName, mAttributionTag, token,
-                mRenouncedPermissions, mNext);
+        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+                token, mAttributionSourceState.renouncedPermissions, getNext());
+    }
+
+    /** @hide */
+    public AttributionSource withPackageName(@Nullable String packageName) {
+        return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(),
+                mAttributionSourceState.renouncedPermissions, getNext());
+    }
+
+    /** @hide */
+    public @NonNull AttributionSourceState asState() {
+        return mAttributionSourceState;
     }
 
     /**
@@ -213,10 +175,9 @@
      * from the caller.
      */
     public void enforceCallingUid() {
-        final int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
-            throw new SecurityException("Calling uid: " + callingUid
-                    + " doesn't match source uid: " + mUid);
+        if (!checkCallingUid()) {
+            throw new SecurityException("Calling uid: " + Binder.getCallingUid()
+                    + " doesn't match source uid: " + mAttributionSourceState.uid);
         }
         // No need to check package as app ops manager does it already.
     }
@@ -231,7 +192,8 @@
      */
     public boolean checkCallingUid() {
         final int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
+        if (callingUid != Process.SYSTEM_UID
+                && callingUid != mAttributionSourceState.uid) {
             return false;
         }
         // No need to check package as app ops manager does it already.
@@ -242,11 +204,12 @@
     public String toString() {
         if (Build.IS_DEBUGGABLE) {
             return "AttributionSource { " +
-                    "uid = " + mUid + ", " +
-                    "packageName = " + mPackageName + ", " +
-                    "attributionTag = " + mAttributionTag + ", " +
-                    "token = " + mToken + ", " +
-                    "next = " + mNext +
+                    "uid = " + mAttributionSourceState.uid + ", " +
+                    "packageName = " + mAttributionSourceState.packageName + ", " +
+                    "attributionTag = " + mAttributionSourceState.attributionTag + ", " +
+                    "token = " + mAttributionSourceState.token + ", " +
+                    "next = " + (mAttributionSourceState.next != null
+                            ? mAttributionSourceState.next[0]: null) +
                     " }";
         }
         return super.toString();
@@ -258,8 +221,8 @@
      * @hide
      */
     public int getNextUid() {
-        if (mNext != null) {
-            return mNext.getUid();
+        if (mAttributionSourceState.next != null) {
+            return mAttributionSourceState.next[0].uid;
         }
         return Process.INVALID_UID;
     }
@@ -270,8 +233,8 @@
      * @hide
      */
     public @Nullable String getNextPackageName() {
-        if (mNext != null) {
-            return mNext.getPackageName();
+        if (mAttributionSourceState.next != null) {
+            return mAttributionSourceState.next[0].packageName;
         }
         return null;
     }
@@ -283,8 +246,8 @@
      * @hide
      */
     public @Nullable String getNextAttributionTag() {
-        if (mNext != null) {
-            return mNext.getAttributionTag();
+        if (mAttributionSourceState.next != null) {
+            return mAttributionSourceState.next[0].attributionTag;
         }
         return null;
     }
@@ -297,8 +260,9 @@
      * @return Whether this is a trusted source.
      */
     public boolean isTrusted(@NonNull Context context) {
-        return mToken != null && context.getSystemService(PermissionManager.class)
-                .isRegisteredAttributionSource(this);
+        return mAttributionSourceState.token != null
+                && context.getSystemService(PermissionManager.class)
+                        .isRegisteredAttributionSource(this);
     }
 
     /**
@@ -310,71 +274,36 @@
     @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
     @NonNull
     public Set<String> getRenouncedPermissions() {
-        return CollectionUtils.emptyIfNull(mRenouncedPermissions);
-    }
-
-    @DataClass.Suppress({"setUid", "setToken"})
-    static class BaseBuilder {}
-
-
-
-
-
-
-    // Code below generated by codegen v1.0.22.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/AttributionSource.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    /* package-private */ AttributionSource(
-            int uid,
-            @Nullable String packageName,
-            @Nullable String attributionTag,
-            @Nullable IBinder token,
-            @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @Nullable Set<String> renouncedPermissions,
-            @Nullable AttributionSource next) {
-        this.mUid = uid;
-        this.mPackageName = packageName;
-        this.mAttributionTag = attributionTag;
-        this.mToken = token;
-        this.mRenouncedPermissions = renouncedPermissions;
-        com.android.internal.util.AnnotationValidations.validate(
-                SystemApi.class, null, mRenouncedPermissions);
-        com.android.internal.util.AnnotationValidations.validate(
-                RequiresPermission.class, null, mRenouncedPermissions,
-                "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
-        this.mNext = next;
-
-        // onConstructed(); // You can define this method to get a callback
+        if (mRenouncedPermissionsCached == null) {
+            if (mAttributionSourceState.renouncedPermissions != null) {
+                mRenouncedPermissionsCached = new ArraySet<>(
+                        mAttributionSourceState.renouncedPermissions);
+            } else {
+                mRenouncedPermissionsCached = Collections.emptySet();
+            }
+        }
+        return mRenouncedPermissionsCached;
     }
 
     /**
      * The UID that is accessing the permission protected data.
      */
     public int getUid() {
-        return mUid;
+        return mAttributionSourceState.uid;
     }
 
     /**
      * The package that is accessing the permission protected data.
      */
     public @Nullable String getPackageName() {
-        return mPackageName;
+        return mAttributionSourceState.packageName;
     }
 
     /**
      * The attribution tag of the app accessing the permission protected data.
      */
     public @Nullable String getAttributionTag() {
-        return mAttributionTag;
+        return mAttributionSourceState.attributionTag;
     }
 
     /**
@@ -383,113 +312,56 @@
      * @hide
      */
     public @Nullable IBinder getToken() {
-        return mToken;
+        return mAttributionSourceState.token;
     }
 
     /**
      * The next app to receive the permission protected data.
      */
     public @Nullable AttributionSource getNext() {
-        return mNext;
+        if (mNextCached == null && mAttributionSourceState.next != null) {
+            mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
+        }
+        return mNextCached;
     }
 
     @Override
     public boolean equals(@Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(AttributionSource other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
         AttributionSource that = (AttributionSource) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mUid == that.mUid
-                && Objects.equals(mPackageName, that.mPackageName)
-                && Objects.equals(mAttributionTag, that.mAttributionTag)
-                && Objects.equals(mToken, that.mToken)
-                && Objects.equals(mRenouncedPermissions, that.mRenouncedPermissions)
-                && Objects.equals(mNext, that.mNext);
+        return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+                && Objects.equals(mAttributionSourceState.packageName,
+                        that.mAttributionSourceState.packageName)
+                && Objects.equals(mAttributionSourceState.attributionTag,
+                        that.mAttributionSourceState.attributionTag)
+                && Objects.equals(mAttributionSourceState.token,
+                        that.mAttributionSourceState.token)
+                && Arrays.equals(mAttributionSourceState.renouncedPermissions,
+                        that.mAttributionSourceState.renouncedPermissions)
+                && Objects.equals(getNext(), that.getNext());
     }
 
     @Override
     public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
         int _hash = 1;
-        _hash = 31 * _hash + mUid;
-        _hash = 31 * _hash + Objects.hashCode(mPackageName);
-        _hash = 31 * _hash + Objects.hashCode(mAttributionTag);
-        _hash = 31 * _hash + Objects.hashCode(mToken);
-        _hash = 31 * _hash + Objects.hashCode(mRenouncedPermissions);
-        _hash = 31 * _hash + Objects.hashCode(mNext);
+        _hash = 31 * _hash + mAttributionSourceState.uid;
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName);
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag);
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token);
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions);
+        _hash = 31 * _hash + Objects.hashCode(getNext());
         return _hash;
     }
 
-    static Parcelling<Set<String>> sParcellingForRenouncedPermissions =
-            Parcelling.Cache.get(
-                    RenouncedPermissionsParcelling.class);
-    static {
-        if (sParcellingForRenouncedPermissions == null) {
-            sParcellingForRenouncedPermissions = Parcelling.Cache.put(
-                    new RenouncedPermissionsParcelling());
-        }
-    }
-
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mPackageName != null) flg |= 0x2;
-        if (mAttributionTag != null) flg |= 0x4;
-        if (mToken != null) flg |= 0x8;
-        if (mRenouncedPermissions != null) flg |= 0x10;
-        if (mNext != null) flg |= 0x20;
-        dest.writeByte(flg);
-        dest.writeInt(mUid);
-        if (mPackageName != null) dest.writeString(mPackageName);
-        if (mAttributionTag != null) dest.writeString(mAttributionTag);
-        if (mToken != null) dest.writeStrongBinder(mToken);
-        sParcellingForRenouncedPermissions.parcel(mRenouncedPermissions, dest, flags);
-        if (mNext != null) dest.writeTypedObject(mNext, flags);
+        mAttributionSourceState.writeToParcel(dest, flags);
     }
 
     @Override
     public int describeContents() { return 0; }
 
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    /* package-private */ AttributionSource(@NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        int uid = in.readInt();
-        String packageName = (flg & 0x2) == 0 ? null : in.readString();
-        String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
-        IBinder token = (flg & 0x8) == 0 ? null : in.readStrongBinder();
-        Set<String> renouncedPermissions = sParcellingForRenouncedPermissions.unparcel(in);
-        AttributionSource next = (flg & 0x20) == 0 ? null : (AttributionSource) in.readTypedObject(AttributionSource.CREATOR);
-
-        this.mUid = uid;
-        this.mPackageName = packageName;
-        this.mAttributionTag = attributionTag;
-        this.mToken = token;
-        this.mRenouncedPermissions = renouncedPermissions;
-        com.android.internal.util.AnnotationValidations.validate(
-                SystemApi.class, null, mRenouncedPermissions);
-        com.android.internal.util.AnnotationValidations.validate(
-                RequiresPermission.class, null, mRenouncedPermissions,
-                "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
-        this.mNext = next;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
     public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR
             = new Parcelable.Creator<AttributionSource>() {
         @Override
@@ -506,15 +378,9 @@
     /**
      * A builder for {@link AttributionSource}
      */
-    @SuppressWarnings("WeakerAccess")
-    public static final class Builder extends BaseBuilder {
-
-        private int mUid;
-        private @Nullable String mPackageName;
-        private @Nullable String mAttributionTag;
-        private @Nullable IBinder mToken;
-        private @Nullable Set<String> mRenouncedPermissions;
-        private @Nullable AttributionSource mNext;
+    public static final class Builder {
+        private @NonNull final AttributionSourceState mAttributionSourceState =
+                new AttributionSourceState();
 
         private long mBuilderFieldsSet = 0L;
 
@@ -524,9 +390,8 @@
          * @param uid
          *   The UID that is accessing the permission protected data.
          */
-        public Builder(
-                int uid) {
-            mUid = uid;
+        public Builder(int uid) {
+            mAttributionSourceState.uid = uid;
         }
 
         /**
@@ -535,7 +400,7 @@
         public @NonNull Builder setPackageName(@Nullable String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2;
-            mPackageName = value;
+            mAttributionSourceState.packageName = value;
             return this;
         }
 
@@ -545,7 +410,7 @@
         public @NonNull Builder setAttributionTag(@Nullable String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x4;
-            mAttributionTag = value;
+            mAttributionSourceState.attributionTag = value;
             return this;
         }
 
@@ -578,7 +443,8 @@
         public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x10;
-            mRenouncedPermissions = value;
+            mAttributionSourceState.renouncedPermissions = (value != null)
+                    ? value.toArray(new String[0]) : null;
             return this;
         }
 
@@ -588,7 +454,8 @@
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
-            mNext = value;
+            mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
+                    {value.mAttributionSourceState} : null;
             return this;
         }
 
@@ -598,28 +465,21 @@
             mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x2) == 0) {
-                mPackageName = null;
+                mAttributionSourceState.packageName = null;
             }
             if ((mBuilderFieldsSet & 0x4) == 0) {
-                mAttributionTag = null;
+                mAttributionSourceState.attributionTag = null;
             }
             if ((mBuilderFieldsSet & 0x8) == 0) {
-                mToken = null;
+                mAttributionSourceState.token = null;
             }
             if ((mBuilderFieldsSet & 0x10) == 0) {
-                mRenouncedPermissions = null;
+                mAttributionSourceState.renouncedPermissions = null;
             }
             if ((mBuilderFieldsSet & 0x20) == 0) {
-                mNext = null;
+                mAttributionSourceState.next = null;
             }
-            AttributionSource o = new AttributionSource(
-                    mUid,
-                    mPackageName,
-                    mAttributionTag,
-                    mToken,
-                    mRenouncedPermissions,
-                    mNext);
-            return o;
+            return new AttributionSource(mAttributionSourceState);
         }
 
         private void checkNotUsed() {
@@ -629,9 +489,4 @@
             }
         }
     }
-
-
-    //@formatter:on
-    // End of generated code
-
 }
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 5089f30..66e0883 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,21 +16,19 @@
 
 package android.content;
 
-import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.Process;
-import android.util.Slog;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.permission.IPermissionChecker;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * This class provides permission check APIs that verify both the
@@ -72,34 +70,44 @@
  * @hide
  */
 public final class PermissionChecker {
-    private static final String LOG_TAG = PermissionChecker.class.getName();
+    /**
+     * The permission is granted.
+     *
+     * @hide
+     */
+    public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
 
-    private static final String PLATFORM_PACKAGE_NAME = "android";
-
-    /** The permission is granted. */
-    public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED;
-
-    /** Only for runtime permissions, its returned when the runtime permission
-     * is granted, but the corresponding app op is denied. */
-    public static final int PERMISSION_SOFT_DENIED = AppOpsManager.MODE_IGNORED;
-
-    /** Returned when:
+    /**
+     * The permission is denied. Applicable only to runtime and app op permissions.
+     *
+     * <p>Returned when:
      * <ul>
-     * <li>For non app op permissions, returned when the permission is denied.</li>
-     * <li>For app op permissions, returned when the app op is denied or app op is
-     * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li>
+     *   <li>the runtime permission is granted, but the corresponding app op is denied
+     *       for runtime permissions.</li>
+     *   <li>the app ops is ignored for app op permissions.</li>
      * </ul>
      *
+     * @hide
      */
-    public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED;
+    public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+
+    /**
+     * The permission is denied.
+     *
+     * <p>Returned when:
+     * <ul>
+     *   <li>the permission is denied for non app op permissions.</li>
+     *   <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT}
+     *   and permission is denied.</li>
+     * </ul>
+     *
+     * @hide
+     */
+    public static final int PERMISSION_HARD_DENIED =  IPermissionChecker.PERMISSION_HARD_DENIED;
 
     /** Constant when the PID for which we check permissions is unknown. */
     public static final int PID_UNKNOWN = -1;
 
-    // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
-    private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
-            = new ConcurrentHashMap<>();
-
     /** @hide */
     @IntDef({PERMISSION_GRANTED,
             PERMISSION_SOFT_DENIED,
@@ -107,6 +115,8 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionResult {}
 
+    private static volatile IPermissionChecker sService;
+
     private PermissionChecker() {
         /* do nothing */
     }
@@ -232,7 +242,7 @@
     public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context,
             @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+        return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
                 message, false /*startDataDelivery*/, /*fromDatasource*/ true);
     }
 
@@ -307,21 +317,23 @@
     public static int checkPermissionForDataDelivery(@NonNull Context context,
             @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean startDataDelivery) {
-        return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+        return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
                 message, startDataDelivery, /*fromDatasource*/ false);
     }
 
     private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
-            @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+            @NonNull String permission, @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
         // If the check failed in the middle of the chain, finish any started op.
-        final int result = checkPermissionCommon(context, permission, attributionSource,
-                message, true /*forDataDelivery*/, startDataDelivery, fromDatasource);
-        if (startDataDelivery && result != PERMISSION_GRANTED) {
-            finishDataDelivery(context, AppOpsManager.permissionToOp(permission),
-                    attributionSource);
+        try {
+            final int result = getPermissionCheckerService().checkPermission(permission,
+                    attributionSource.asState(), message, true /*forDataDelivery*/,
+                    startDataDelivery, fromDatasource);
+            return result;
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
-        return result;
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -356,9 +368,14 @@
     public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
             @NonNull String permission, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkPermissionCommon(context, permission, attributionSource,
-                message, true /*forDataDelivery*/, /*startDataDelivery*/ true,
-                /*fromDatasource*/ false);
+        try {
+            return getPermissionCheckerService().checkPermission(permission,
+                    attributionSource.asState(), message, true /*forDataDelivery*/,
+                    /*startDataDelivery*/ true, /*fromDatasource*/ false);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -390,13 +407,14 @@
     public static int startOpForDataDelivery(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        final int result = checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
-                message, true /*forDataDelivery*/, true /*startDataDelivery*/);
-        // It is important to finish any started op if some step in the attribution chain failed.
-        if (result != PERMISSION_GRANTED) {
-            finishDataDelivery(context, opName, attributionSource);
+        try {
+            return getPermissionCheckerService().checkOp(
+                    AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+                    true /*forDataDelivery*/, true /*startDataDelivery*/);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
-        return result;
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -412,15 +430,10 @@
      */
     public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
             @NonNull AttributionSource attributionSource) {
-        if (op == null || attributionSource.getPackageName() == null) {
-            return;
-        }
-
-        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-        appOpsManager.finishProxyOp(op, attributionSource);
-
-        if (attributionSource.getNext() != null) {
-            finishDataDelivery(context, op, attributionSource.getNext());
+        try {
+            getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
     }
 
@@ -456,8 +469,14 @@
     public static int checkOpForPreflight(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
-                message,  false /*forDataDelivery*/, false /*startDataDelivery*/);
+        try {
+            return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
+                    attributionSource.asState(), message, false /*forDataDelivery*/,
+                    false /*startDataDelivery*/);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -489,8 +508,14 @@
     public static int checkOpForDataDelivery(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
-                message,  true /*forDataDelivery*/, false /*startDataDelivery*/);
+        try {
+            return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
+                    attributionSource.asState(), message, true /*forDataDelivery*/,
+                    false /*startDataDelivery*/);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -561,9 +586,14 @@
     @PermissionResult
     public static int checkPermissionForPreflight(@NonNull Context context,
             @NonNull String permission, @NonNull AttributionSource attributionSource) {
-        return checkPermissionCommon(context, permission, attributionSource,
-                null /*message*/, false /*forDataDelivery*/, /*startDataDelivery*/ false,
-                /*fromDatasource*/ false);
+        try {
+            return getPermissionCheckerService().checkPermission(permission,
+                    attributionSource.asState(), null /*message*/, false /*forDataDelivery*/,
+                    /*startDataDelivery*/ false, /*fromDatasource*/ false);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -798,356 +828,12 @@
                 Binder.getCallingUid(), packageName);
     }
 
-    @PermissionResult
-    private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
-            @NonNull AttributionSource attributionSource,
-            @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
-            boolean fromDatasource) {
-        PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
-
-        if (permissionInfo == null) {
-            try {
-                permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
-                if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
-                    // Double addition due to concurrency is fine - the backing store is concurrent.
-                    sPlatformPermissions.put(permission, permissionInfo);
-                }
-            } catch (PackageManager.NameNotFoundException ignored) {
-                return PERMISSION_HARD_DENIED;
-            }
+    private static @NonNull IPermissionChecker getPermissionCheckerService() {
+        // Race is fine, we may end up looking up the same instance twice, no big deal.
+        if (sService == null) {
+            final IBinder service = ServiceManager.getService("permission_checker");
+            sService = IPermissionChecker.Stub.asInterface(service);
         }
-
-        if (permissionInfo.isAppOp()) {
-            return checkAppOpPermission(context, permission, attributionSource, message,
-                    forDataDelivery, fromDatasource);
-        }
-        if (permissionInfo.isRuntime()) {
-            return checkRuntimePermission(context, permission, attributionSource, message,
-                    forDataDelivery, startDataDelivery, fromDatasource);
-        }
-
-        if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
-                attributionSource.getRenouncedPermissions())) {
-            return PERMISSION_HARD_DENIED;
-        }
-
-        if (attributionSource.getNext() != null) {
-            return checkPermissionCommon(context, permission,
-                    attributionSource.getNext(), message, forDataDelivery,
-                    startDataDelivery, /*fromDatasource*/ false);
-        }
-
-        return PERMISSION_GRANTED;
-    }
-
-    @PermissionResult
-    private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean fromDatasource) {
-        final int op = AppOpsManager.permissionToOpCode(permission);
-        if (op < 0) {
-            Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!");
-            return PERMISSION_HARD_DENIED;
-        }
-
-        AttributionSource current = attributionSource;
-        AttributionSource next = null;
-
-        while (true) {
-            final boolean skipCurrentChecks = (fromDatasource || next != null);
-
-            next = current.getNext();
-
-            // If the call is from a datasource we need to vet only the chain before it. This
-            // way we can avoid the datasource creating an attribution context for every call.
-            if (!(fromDatasource && current == attributionSource)
-                    && next != null && !current.isTrusted(context)) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            // The access is for oneself if this is the single receiver of data
-            // after the data source or if this is the single attribution source
-            // in the chain if not from a datasource.
-            final boolean singleReceiverFromDatasource = (fromDatasource
-                    && current == attributionSource && next != null && next.getNext() == null);
-            final boolean selfAccess = singleReceiverFromDatasource || next == null;
-
-            final int opMode = performOpTransaction(context, op, current, message,
-                    forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
-                    selfAccess, singleReceiverFromDatasource);
-
-            switch (opMode) {
-                case AppOpsManager.MODE_IGNORED:
-                case AppOpsManager.MODE_ERRORED: {
-                    return PERMISSION_HARD_DENIED;
-                }
-                case AppOpsManager.MODE_DEFAULT: {
-                    if (!skipCurrentChecks && !checkPermission(context, permission,
-                            attributionSource.getUid(), attributionSource
-                                    .getRenouncedPermissions())) {
-                        return PERMISSION_HARD_DENIED;
-                    }
-                    if (next != null && !checkPermission(context, permission,
-                            next.getUid(), next.getRenouncedPermissions())) {
-                        return PERMISSION_HARD_DENIED;
-                    }
-                }
-            }
-
-            if (next == null || next.getNext() == null) {
-                return PERMISSION_GRANTED;
-            }
-
-            current = next;
-        }
-    }
-
-    private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
-        // Now let's check the identity chain...
-        final int op = AppOpsManager.permissionToOpCode(permission);
-
-        AttributionSource current = attributionSource;
-        AttributionSource next = null;
-
-        while (true) {
-            final boolean skipCurrentChecks = (fromDatasource || next != null);
-            next = current.getNext();
-
-            // If the call is from a datasource we need to vet only the chain before it. This
-            // way we can avoid the datasource creating an attribution context for every call.
-            if (!(fromDatasource && current == attributionSource)
-                    && next != null && !current.isTrusted(context)) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            // If we already checked the permission for this one, skip the work
-            if (!skipCurrentChecks && !checkPermission(context, permission,
-                    current.getUid(), current.getRenouncedPermissions())) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            if (next != null && !checkPermission(context, permission,
-                    next.getUid(), next.getRenouncedPermissions())) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            if (op < 0) {
-                // Bg location is one-off runtime modifier permission and has no app op
-                if (sPlatformPermissions.contains(permission)
-                        && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
-                    Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
-                            + " with no app op defined!");
-                }
-                if (next == null) {
-                    return PERMISSION_GRANTED;
-                }
-                current = next;
-                continue;
-            }
-
-            // The access is for oneself if this is the single receiver of data
-            // after the data source or if this is the single attribution source
-            // in the chain if not from a datasource.
-            final boolean singleReceiverFromDatasource = (fromDatasource
-                    && current == attributionSource && next != null && next.getNext() == null);
-            final boolean selfAccess = singleReceiverFromDatasource || next == null;
-
-            final int opMode = performOpTransaction(context, op, current, message,
-                    forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                    singleReceiverFromDatasource);
-
-            switch (opMode) {
-                case AppOpsManager.MODE_ERRORED: {
-                    return PERMISSION_HARD_DENIED;
-                }
-                case AppOpsManager.MODE_IGNORED: {
-                    return PERMISSION_SOFT_DENIED;
-                }
-            }
-
-            if (next == null || next.getNext() == null) {
-                return PERMISSION_GRANTED;
-            }
-
-            current = next;
-        }
-    }
-
-    private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
-            int uid, @NonNull Set<String> renouncedPermissions) {
-        final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
-                uid) == PackageManager.PERMISSION_GRANTED;
-        if (permissionGranted && renouncedPermissions.contains(permission)
-                && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
-                        /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
-            return false;
-        }
-        return permissionGranted;
-    }
-
-    private static int checkOp(@NonNull Context context, @NonNull int op,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean startDataDelivery) {
-        if (op < 0 || attributionSource.getPackageName() == null) {
-            return PERMISSION_HARD_DENIED;
-        }
-
-        AttributionSource current = attributionSource;
-        AttributionSource next = null;
-
-        while (true) {
-            final boolean skipCurrentChecks = (next != null);
-            next = current.getNext();
-
-            // If the call is from a datasource we need to vet only the chain before it. This
-            // way we can avoid the datasource creating an attribution context for every call.
-            if (next != null && !current.isTrusted(context)) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            // The access is for oneself if this is the single attribution source in the chain.
-            final boolean selfAccess = (next == null);
-
-            final int opMode = performOpTransaction(context, op, current, message,
-                    forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                    /*fromDatasource*/ false);
-
-            switch (opMode) {
-                case AppOpsManager.MODE_ERRORED: {
-                    return PERMISSION_HARD_DENIED;
-                }
-                case AppOpsManager.MODE_IGNORED: {
-                    return PERMISSION_SOFT_DENIED;
-                }
-            }
-
-            if (next == null || next.getNext() == null) {
-                return PERMISSION_GRANTED;
-            }
-
-            current = next;
-        }
-    }
-
-    private static int performOpTransaction(@NonNull Context context, int op,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
-            boolean selfAccess, boolean singleReceiverFromDatasource) {
-        // We cannot perform app ops transactions without a package name. In all relevant
-        // places we pass the package name but just in case there is a bug somewhere we
-        // do a best effort to resolve the package from the UID (pick first without a loss
-        // of generality - they are in the same security sandbox).
-        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-        final AttributionSource accessorSource = (!singleReceiverFromDatasource)
-                ? attributionSource : attributionSource.getNext();
-        if (!forDataDelivery) {
-            final String resolvedAccessorPackageName = resolvePackageName(context, accessorSource);
-            if (resolvedAccessorPackageName == null) {
-                return AppOpsManager.MODE_ERRORED;
-            }
-            final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
-                    accessorSource.getUid(), resolvedAccessorPackageName);
-            final AttributionSource next = accessorSource.getNext();
-            if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
-                final String resolvedNextPackageName = resolvePackageName(context, next);
-                if (resolvedNextPackageName == null) {
-                    return AppOpsManager.MODE_ERRORED;
-                }
-                return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
-                        resolvedNextPackageName);
-            }
-            return opMode;
-        } else if (startDataDelivery) {
-            final AttributionSource resolvedAttributionSource = resolveAttributionSource(
-                    context, accessorSource);
-            if (resolvedAttributionSource.getPackageName() == null) {
-                return AppOpsManager.MODE_ERRORED;
-            }
-            if (selfAccess) {
-                // If the datasource is not in a trusted platform component then in would not
-                // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
-                // an app is exposing runtime permission protected data but cannot blame others
-                // in a trusted way which would not properly show in permission usage UIs.
-                // As a fallback we note a proxy op that blames the app and the datasource.
-                try {
-                    return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
-                            resolvedAttributionSource.getPackageName(),
-                            /*startIfModeDefault*/ false,
-                            resolvedAttributionSource.getAttributionTag(),
-                            message);
-                } catch (SecurityException e) {
-                    Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
-                            + " platform defined runtime permission "
-                            + AppOpsManager.opToPermission(op) + " while not having "
-                            + Manifest.permission.UPDATE_APP_OPS_STATS);
-                    return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
-                            skipProxyOperation);
-                }
-            } else {
-                return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
-                        skipProxyOperation);
-            }
-        } else {
-            final AttributionSource resolvedAttributionSource = resolveAttributionSource(
-                    context, accessorSource);
-            if (resolvedAttributionSource.getPackageName() == null) {
-                return AppOpsManager.MODE_ERRORED;
-            }
-            if (selfAccess) {
-                // If the datasource is not in a trusted platform component then in would not
-                // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
-                // an app is exposing runtime permission protected data but cannot blame others
-                // in a trusted way which would not properly show in permission usage UIs.
-                // As a fallback we note a proxy op that blames the app and the datasource.
-                try {
-                    return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
-                            resolvedAttributionSource.getPackageName(),
-                            resolvedAttributionSource.getAttributionTag(),
-                            message);
-                } catch (SecurityException e) {
-                    Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
-                            + " platform defined runtime permission "
-                            + AppOpsManager.opToPermission(op) + " while not having "
-                            + Manifest.permission.UPDATE_APP_OPS_STATS);
-                    return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
-                            skipProxyOperation);
-                }
-            } else {
-                return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
-                        skipProxyOperation);
-            }
-        }
-    }
-
-    private static @Nullable String resolvePackageName(@NonNull Context context,
-            @NonNull AttributionSource attributionSource) {
-        if (attributionSource.getPackageName() != null) {
-            return attributionSource.getPackageName();
-        }
-        final String[] packageNames = context.getPackageManager().getPackagesForUid(
-                attributionSource.getUid());
-        if (packageNames != null) {
-            // This is best effort if the caller doesn't pass a package. The security
-            // sandbox is UID, therefore we pick an arbitrary package.
-            return packageNames[0];
-        }
-        // Last resort to handle special UIDs like root, etc.
-        return AppOpsManager.resolvePackageName(attributionSource.getUid(),
-                attributionSource.getPackageName());
-    }
-
-    private static @NonNull AttributionSource resolveAttributionSource(
-            @NonNull Context context, @NonNull AttributionSource attributionSource) {
-        if (attributionSource.getPackageName() != null) {
-            return attributionSource;
-        }
-        return new AttributionSource(attributionSource.getUid(),
-                resolvePackageName(context, attributionSource),
-                attributionSource.getAttributionTag(),
-                attributionSource.getToken(),
-                attributionSource.getRenouncedPermissions(),
-                attributionSource.getNext());
+        return sService;
     }
 }
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index d026e95..2a9b703 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -560,9 +560,6 @@
             }
         }
 
-        final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
-                AppOpsManager.pauseNotedAppOpsCollection();
-
         if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isListeningForOpNoted()) {
             flags |= FLAG_COLLECT_NOTED_APP_OPS;
         }
@@ -571,8 +568,6 @@
             boolean replyOwnsNative = (reply == null) ? false : reply.ownsNativeParcelObject();
             return transactNative(code, data, reply, replyOwnsNative, flags);
         } finally {
-            AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
-
             if (transactListener != null) {
                 transactListener.onTransactEnded(session);
             }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7eaf18f..c73593f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3085,7 +3085,8 @@
         String resolveProxyPackageName = AppOpsManager.resolvePackageName(proxyUid,
                 proxyPackageName);
         if (resolveProxyPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code,
+                    proxiedAttributionTag, proxiedPackageName);
         }
 
         final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
@@ -3101,14 +3102,16 @@
                     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);
+                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);
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+                    proxiedPackageName);
         }
 
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
@@ -3135,7 +3138,8 @@
 
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+                    packageName);
         }
         return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
                 Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
@@ -3152,7 +3156,8 @@
             bypass = verifyAndGetBypass(uid, packageName, attributionTag);
         } catch (SecurityException e) {
             Slog.e(TAG, "noteOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                    packageName);
         }
 
         synchronized (this) {
@@ -3163,7 +3168,8 @@
                         AppOpsManager.MODE_IGNORED);
                 if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName);
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                        packageName);
             }
             final Op op = getOpLocked(ops, code, uid, true);
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
@@ -3179,7 +3185,8 @@
                 attributedOp.rejected(uidState.state, flags);
                 scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                         AppOpsManager.MODE_IGNORED);
-                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+                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.
@@ -3192,7 +3199,7 @@
                     attributedOp.rejected(uidState.state, flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                             uidMode);
-                    return new SyncNotedAppOp(uidMode, code, attributionTag);
+                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
             } else {
                 final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
@@ -3205,7 +3212,7 @@
                     attributedOp.rejected(uidState.state, flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                             mode);
-                    return new SyncNotedAppOp(mode, code, attributionTag);
+                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
             }
             if (DEBUG) {
@@ -3224,7 +3231,8 @@
                         shouldCollectMessage);
             }
 
-            return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
+                    packageName);
         }
     }
 
@@ -3528,7 +3536,8 @@
 
         String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+                    packageName);
         }
 
         // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
@@ -3539,7 +3548,7 @@
         if (code == OP_RECORD_AUDIO_HOTWORD) {
             int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
             if (result != AppOpsManager.MODE_ALLOWED) {
-                return new SyncNotedAppOp(result, code, attributionTag);
+                return new SyncNotedAppOp(result, code, attributionTag, packageName);
             }
         }
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
@@ -3578,7 +3587,8 @@
         String resolvedProxyPackageName = AppOpsManager.resolvePackageName(proxyUid,
                 proxyPackageName);
         if (resolvedProxyPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+                    proxiedPackageName);
         }
 
         final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
@@ -3589,7 +3599,8 @@
         String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
                 proxiedPackageName);
         if (resolvedProxiedPackageName == null) {
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+                    proxiedPackageName);
         }
 
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
@@ -3637,7 +3648,8 @@
             bypass = verifyAndGetBypass(uid, packageName, attributionTag);
         } catch (SecurityException e) {
             Slog.e(TAG, "startOperation", e);
-            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                    packageName);
         }
 
         synchronized (this) {
@@ -3649,7 +3661,8 @@
                 }
                 if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName);
-                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                        packageName);
             }
             final Op op = getOpLocked(ops, code, uid, true);
             if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
@@ -3657,7 +3670,8 @@
                     scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
                             flags, AppOpsManager.MODE_IGNORED);
                 }
-                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+                return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+                        packageName);
             }
 
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
@@ -3678,7 +3692,7 @@
                         scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
                                 flags, uidMode);
                     }
-                    return new SyncNotedAppOp(uidMode, code, attributionTag);
+                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
             } else {
                 final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
@@ -3694,7 +3708,7 @@
                         scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
                                 flags, mode);
                     }
-                    return new SyncNotedAppOp(mode, code, attributionTag);
+                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
             }
             if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
@@ -3716,7 +3730,8 @@
                     message, shouldCollectMessage);
         }
 
-        return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag);
+        return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
+                packageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 884bbea..b3fa98e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -70,7 +70,9 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.AttributionSource;
+import android.content.AttributionSourceState;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
@@ -104,6 +106,7 @@
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.IPermissionChecker;
 import android.permission.IPermissionManager;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
@@ -164,6 +167,7 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -451,6 +455,7 @@
         if (permissionService == null) {
             permissionService = new PermissionManagerService(context, availableFeatures);
             ServiceManager.addService("permissionmgr", permissionService);
+            ServiceManager.addService("permission_checker", new PermissionCheckerService(context));
         }
         return LocalServices.getService(PermissionManagerServiceInternal.class);
     }
@@ -5448,4 +5453,419 @@
             }
         }
     }
+
+    /**
+     * TODO: We need to consolidate these APIs either on PermissionManager or an extension
+     * object or a separate PermissionChecker service in context. The impartant part is to
+     * keep a single impl that is exposed to Java and native. We are not sure about the
+     * API shape so let is soak a bit.
+     */
+    private static final class PermissionCheckerService extends IPermissionChecker.Stub {
+        // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
+        private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
+                = new ConcurrentHashMap<>();
+
+        private final @NonNull Context mContext;
+        private final @NonNull AppOpsManager mAppOpsManager;
+
+        PermissionCheckerService(@NonNull Context context) {
+            mContext = context;
+            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        }
+
+        @Override
+        @PermissionChecker.PermissionResult
+        public int checkPermission(@NonNull String permission,
+                @NonNull AttributionSourceState attributionSourceState, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+            Objects.requireNonNull(permission);
+            Objects.requireNonNull(attributionSourceState);
+            final AttributionSource attributionSource = new AttributionSource(
+                    attributionSourceState);
+            final int result = checkPermission(mContext, permission, attributionSource, message,
+                    forDataDelivery, startDataDelivery, fromDatasource);
+            // Finish any started op if some step in the attribution chain failed.
+            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+                finishDataDelivery(AppOpsManager.permissionToOp(permission),
+                        attributionSource.asState());
+            }
+            return result;
+        }
+
+        @Override
+        public void finishDataDelivery(@NonNull String op,
+                @NonNull AttributionSourceState attributionSourceState) {
+            if (op == null || attributionSourceState.packageName == null) {
+                return;
+            }
+            mAppOpsManager.finishProxyOp(op, new AttributionSource(attributionSourceState));
+            if (attributionSourceState.next != null) {
+                finishDataDelivery(op, attributionSourceState.next[0]);
+            }
+        }
+
+        @Override
+        @PermissionChecker.PermissionResult
+        public int checkOp(int op, AttributionSourceState attributionSource,
+                String message, boolean forDataDelivery, boolean startDataDelivery) {
+            int result = checkOp(mContext, op, new AttributionSource(attributionSource), message,
+                    forDataDelivery, startDataDelivery);
+            if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) {
+                // Finish any started op if some step in the attribution chain failed.
+                finishDataDelivery(AppOpsManager.opToName(op), attributionSource);
+            }
+            return  result;
+        }
+
+        @PermissionChecker.PermissionResult
+        private static int checkPermission(@NonNull Context context, @NonNull String permission,
+                @NonNull AttributionSource attributionSource, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+            PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
+
+            if (permissionInfo == null) {
+                try {
+                    permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+                    if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+                        // Double addition due to concurrency is fine - the backing
+                        // store is concurrent.
+                        sPlatformPermissions.put(permission, permissionInfo);
+                    }
+                } catch (PackageManager.NameNotFoundException ignored) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+            }
+
+            if (permissionInfo.isAppOp()) {
+                return checkAppOpPermission(context, permission, attributionSource, message,
+                        forDataDelivery, fromDatasource);
+            }
+            if (permissionInfo.isRuntime()) {
+                return checkRuntimePermission(context, permission, attributionSource, message,
+                        forDataDelivery, startDataDelivery, fromDatasource);
+            }
+
+            if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
+                    attributionSource.getRenouncedPermissions())) {
+                return PermissionChecker.PERMISSION_HARD_DENIED;
+            }
+
+            if (attributionSource.getNext() != null) {
+                return checkPermission(context, permission,
+                        attributionSource.getNext(), message, forDataDelivery,
+                        startDataDelivery, /*fromDatasource*/ false);
+            }
+
+            return PermissionChecker.PERMISSION_GRANTED;
+        }
+
+        @PermissionChecker.PermissionResult
+        private static int checkAppOpPermission(@NonNull Context context,
+                @NonNull String permission, @NonNull AttributionSource attributionSource,
+                @Nullable String message, boolean forDataDelivery, boolean fromDatasource) {
+            final int op = AppOpsManager.permissionToOpCode(permission);
+            if (op < 0) {
+                Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!");
+                return PermissionChecker.PERMISSION_HARD_DENIED;
+            }
+
+            AttributionSource current = attributionSource;
+            AttributionSource next = null;
+
+            while (true) {
+                final boolean skipCurrentChecks = (fromDatasource || next != null);
+
+                next = current.getNext();
+
+                // If the call is from a datasource we need to vet only the chain before it. This
+                // way we can avoid the datasource creating an attribution context for every call.
+                if (!(fromDatasource && current == attributionSource)
+                        && next != null && !current.isTrusted(context)) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                // The access is for oneself if this is the single receiver of data
+                // after the data source or if this is the single attribution source
+                // in the chain if not from a datasource.
+                final boolean singleReceiverFromDatasource = (fromDatasource
+                        && current == attributionSource && next != null && next.getNext() == null);
+                final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+                final int opMode = performOpTransaction(context, op, current, message,
+                        forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
+                        selfAccess, singleReceiverFromDatasource);
+
+                switch (opMode) {
+                    case AppOpsManager.MODE_IGNORED:
+                    case AppOpsManager.MODE_ERRORED: {
+                        return PermissionChecker.PERMISSION_HARD_DENIED;
+                    }
+                    case AppOpsManager.MODE_DEFAULT: {
+                        if (!skipCurrentChecks && !checkPermission(context, permission,
+                                attributionSource.getUid(), attributionSource
+                                        .getRenouncedPermissions())) {
+                            return PermissionChecker.PERMISSION_HARD_DENIED;
+                        }
+                        if (next != null && !checkPermission(context, permission,
+                                next.getUid(), next.getRenouncedPermissions())) {
+                            return PermissionChecker.PERMISSION_HARD_DENIED;
+                        }
+                    }
+                }
+
+                if (next == null || next.getNext() == null) {
+                    return PermissionChecker.PERMISSION_GRANTED;
+                }
+
+                current = next;
+            }
+        }
+
+        private static int checkRuntimePermission(@NonNull Context context,
+                @NonNull String permission, @NonNull AttributionSource attributionSource,
+                @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
+                boolean fromDatasource) {
+            // Now let's check the identity chain...
+            final int op = AppOpsManager.permissionToOpCode(permission);
+
+            AttributionSource current = attributionSource;
+            AttributionSource next = null;
+
+            while (true) {
+                final boolean skipCurrentChecks = (fromDatasource || next != null);
+                next = current.getNext();
+
+                // If the call is from a datasource we need to vet only the chain before it. This
+                // way we can avoid the datasource creating an attribution context for every call.
+                if (!(fromDatasource && current == attributionSource)
+                        && next != null && !current.isTrusted(context)) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                // If we already checked the permission for this one, skip the work
+                if (!skipCurrentChecks && !checkPermission(context, permission,
+                        current.getUid(), current.getRenouncedPermissions())) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                if (next != null && !checkPermission(context, permission,
+                        next.getUid(), next.getRenouncedPermissions())) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                if (op < 0) {
+                    // Bg location is one-off runtime modifier permission and has no app op
+                    if (sPlatformPermissions.contains(permission)
+                            && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+                        Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
+                                + " with no app op defined!");
+                    }
+                    if (next == null) {
+                        return PermissionChecker.PERMISSION_GRANTED;
+                    }
+                    current = next;
+                    continue;
+                }
+
+                // The access is for oneself if this is the single receiver of data
+                // after the data source or if this is the single attribution source
+                // in the chain if not from a datasource.
+                final boolean singleReceiverFromDatasource = (fromDatasource
+                        && current == attributionSource && next != null && next.getNext() == null);
+                final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+                final int opMode = performOpTransaction(context, op, current, message,
+                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+                        singleReceiverFromDatasource);
+
+                switch (opMode) {
+                    case AppOpsManager.MODE_ERRORED: {
+                        return PermissionChecker.PERMISSION_HARD_DENIED;
+                    }
+                    case AppOpsManager.MODE_IGNORED: {
+                        return PermissionChecker.PERMISSION_SOFT_DENIED;
+                    }
+                }
+
+                if (next == null || next.getNext() == null) {
+                    return PermissionChecker.PERMISSION_GRANTED;
+                }
+
+                current = next;
+            }
+        }
+
+        private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
+                int uid, @NonNull Set<String> renouncedPermissions) {
+            final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
+                    uid) == PackageManager.PERMISSION_GRANTED;
+            if (permissionGranted && renouncedPermissions.contains(permission)
+                    && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
+                    /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+            return permissionGranted;
+        }
+
+        private static int checkOp(@NonNull Context context, @NonNull int op,
+                @NonNull AttributionSource attributionSource, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery) {
+            if (op < 0 || attributionSource.getPackageName() == null) {
+                return PermissionChecker.PERMISSION_HARD_DENIED;
+            }
+
+            AttributionSource current = attributionSource;
+            AttributionSource next = null;
+
+            while (true) {
+                final boolean skipCurrentChecks = (next != null);
+                next = current.getNext();
+
+                // If the call is from a datasource we need to vet only the chain before it. This
+                // way we can avoid the datasource creating an attribution context for every call.
+                if (next != null && !current.isTrusted(context)) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                // The access is for oneself if this is the single attribution source in the chain.
+                final boolean selfAccess = (next == null);
+
+                final int opMode = performOpTransaction(context, op, current, message,
+                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+                        /*fromDatasource*/ false);
+
+                switch (opMode) {
+                    case AppOpsManager.MODE_ERRORED: {
+                        return PermissionChecker.PERMISSION_HARD_DENIED;
+                    }
+                    case AppOpsManager.MODE_IGNORED: {
+                        return PermissionChecker.PERMISSION_SOFT_DENIED;
+                    }
+                }
+
+                if (next == null || next.getNext() == null) {
+                    return PermissionChecker.PERMISSION_GRANTED;
+                }
+
+                current = next;
+            }
+        }
+
+        private static int performOpTransaction(@NonNull Context context, int op,
+                @NonNull AttributionSource attributionSource, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
+                boolean selfAccess, boolean singleReceiverFromDatasource) {
+            // We cannot perform app ops transactions without a package name. In all relevant
+            // places we pass the package name but just in case there is a bug somewhere we
+            // do a best effort to resolve the package from the UID (pick first without a loss
+            // of generality - they are in the same security sandbox).
+            final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+            final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+                    ? attributionSource : attributionSource.getNext();
+            if (!forDataDelivery) {
+                final String resolvedAccessorPackageName = resolvePackageName(context,
+                        accessorSource);
+                if (resolvedAccessorPackageName == null) {
+                    return AppOpsManager.MODE_ERRORED;
+                }
+                final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
+                        accessorSource.getUid(), resolvedAccessorPackageName);
+                final AttributionSource next = accessorSource.getNext();
+                if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
+                    final String resolvedNextPackageName = resolvePackageName(context, next);
+                    if (resolvedNextPackageName == null) {
+                        return AppOpsManager.MODE_ERRORED;
+                    }
+                    return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
+                            resolvedNextPackageName);
+                }
+                return opMode;
+            } else if (startDataDelivery) {
+                final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+                        context, accessorSource);
+                if (resolvedAttributionSource.getPackageName() == null) {
+                    return AppOpsManager.MODE_ERRORED;
+                }
+                if (selfAccess) {
+                    // If the datasource is not in a trusted platform component then in would not
+                    // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+                    // an app is exposing runtime permission protected data but cannot blame others
+                    // in a trusted way which would not properly show in permission usage UIs.
+                    // As a fallback we note a proxy op that blames the app and the datasource.
+                    try {
+                        return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
+                                resolvedAttributionSource.getPackageName(),
+                                /*startIfModeDefault*/ false,
+                                resolvedAttributionSource.getAttributionTag(),
+                                message);
+                    } catch (SecurityException e) {
+                        Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+                                + " platform defined runtime permission "
+                                + AppOpsManager.opToPermission(op) + " while not having "
+                                + Manifest.permission.UPDATE_APP_OPS_STATS);
+                        return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
+                                skipProxyOperation);
+                    }
+                } else {
+                    return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
+                            skipProxyOperation);
+                }
+            } else {
+                final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+                        context, accessorSource);
+                if (resolvedAttributionSource.getPackageName() == null) {
+                    return AppOpsManager.MODE_ERRORED;
+                }
+                if (selfAccess) {
+                    // If the datasource is not in a trusted platform component then in would not
+                    // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+                    // an app is exposing runtime permission protected data but cannot blame others
+                    // in a trusted way which would not properly show in permission usage UIs.
+                    // As a fallback we note a proxy op that blames the app and the datasource.
+                    try {
+                        return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
+                                resolvedAttributionSource.getPackageName(),
+                                resolvedAttributionSource.getAttributionTag(),
+                                message);
+                    } catch (SecurityException e) {
+                        Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+                                + " platform defined runtime permission "
+                                + AppOpsManager.opToPermission(op) + " while not having "
+                                + Manifest.permission.UPDATE_APP_OPS_STATS);
+                        return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
+                                skipProxyOperation);
+                    }
+                } else {
+                    return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
+                            skipProxyOperation);
+                }
+            }
+        }
+
+        private static @Nullable String resolvePackageName(@NonNull Context context,
+                @NonNull AttributionSource attributionSource) {
+            if (attributionSource.getPackageName() != null) {
+                return attributionSource.getPackageName();
+            }
+            final String[] packageNames = context.getPackageManager().getPackagesForUid(
+                    attributionSource.getUid());
+            if (packageNames != null) {
+                // This is best effort if the caller doesn't pass a package. The security
+                // sandbox is UID, therefore we pick an arbitrary package.
+                return packageNames[0];
+            }
+            // Last resort to handle special UIDs like root, etc.
+            return AppOpsManager.resolvePackageName(attributionSource.getUid(),
+                    attributionSource.getPackageName());
+        }
+
+        private static @NonNull AttributionSource resolveAttributionSource(
+                @NonNull Context context, @NonNull AttributionSource attributionSource) {
+            if (attributionSource.getPackageName() != null) {
+                return attributionSource;
+            }
+            return attributionSource.withPackageName(resolvePackageName(context,
+                    attributionSource));
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 41237c8..12e0d8b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -168,7 +168,7 @@
 
     private void mockNoteOperation() {
         SyncNotedAppOp allowed = new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED,
-                AppOpsManager.OP_GET_USAGE_STATS, null);
+                AppOpsManager.OP_GET_USAGE_STATS, null, mContext.getPackageName());
         when(mAppOpsService.noteOperation(eq(AppOpsManager.OP_GET_USAGE_STATS), eq(Process.myUid()),
                 nullable(String.class), nullable(String.class), any(Boolean.class),
                 nullable(String.class), any(Boolean.class))).thenReturn(allowed);