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.

bug: 158792096

Test: atest CtsPermission5TestCases

Change-Id: I4e050e78b2361cbf524cc213802e0fef5b487f67
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/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/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 17c90d6..64d5d9c 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -20,6 +20,7 @@
 
 import android.Manifest;
 import android.annotation.CheckResult;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,6 +32,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.ChangeId;
@@ -63,6 +65,8 @@
 import com.android.internal.annotations.Immutable;
 import com.android.internal.util.CollectionUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
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 2d1178a..0147790 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);
     }
@@ -5414,4 +5419,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));
+        }
+    }
 }