Merge "Remove color font and icons overlays" into sc-dev
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
new file mode 100644
index 0000000..6e81afc
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * It contains all the keys for the flags, as well as caches some of latest flag values from
+ * DeviceConfig.
+ *
+ * <p>Though the latest flag values can always be retrieved by calling {@code
+ * DeviceConfig.getProperty}, we want to cache some of those values. For example, the sampling
+ * intervals for logging, they are needed for each api call and it would be a little expensive to
+ * call
+ * {@code DeviceConfig.getProperty} every time.
+ *
+ * <p>Listener is registered to DeviceConfig keep the cached value up to date.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @hide
+ */
+public final class AppSearchConfig implements AutoCloseable {
+    /**
+     * It would be used as default min time interval between samples in millis if there is no value
+     * set for {@link AppSearchConfig#KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS} in DeviceConfig.
+     */
+    @VisibleForTesting
+    static final long DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 50;
+
+    /**
+     * It would be used as default sampling interval if there is no value
+     * set for {@link AppSearchConfig#KEY_SAMPLING_INTERVAL_DEFAULT} in DeviceConfig.
+     */
+    @VisibleForTesting
+    static final int DEFAULT_SAMPLING_INTERVAL = 10;
+
+    /*
+     * Keys for ALL the flags stored in DeviceConfig.
+     */
+    public static final String KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS =
+            "min_time_interval_between_samples_millis";
+    public static final String KEY_SAMPLING_INTERVAL_DEFAULT = "sampling_interval_default";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS =
+            "sampling_interval_for_batch_call_stats";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
+            "sampling_interval_for_put_document_stats";
+
+    // Array contains all the corresponding keys for the cached values.
+    private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
+            KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+            KEY_SAMPLING_INTERVAL_DEFAULT,
+            KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+            KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS
+    };
+
+    // Lock needed for all the operations in this class.
+    private final Object mLock = new Object();
+
+    /**
+     * Bundle to hold all the cached flag values corresponding to
+     * {@link AppSearchConfig#KEYS_TO_ALL_CACHED_VALUES}.
+     */
+    @GuardedBy("mLock")
+    private final Bundle mBundleLocked = new Bundle();
+
+
+    @GuardedBy("mLock")
+    private boolean mIsClosedLocked = false;
+
+    /** Listener to update cached flag values from DeviceConfig. */
+    private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
+            properties -> {
+                if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_APPSEARCH)) {
+                    return;
+                }
+
+                updateCachedValues(properties);
+            };
+
+    /**
+     * Creates an instance of {@link AppSearchConfig}.
+     *
+     * @param executor used to fetch and cache the flag values from DeviceConfig during creation or
+     *                 config change.
+     */
+    @NonNull
+    public static AppSearchConfig create(@NonNull Executor executor) {
+        Objects.requireNonNull(executor);
+        AppSearchConfig configManager = new AppSearchConfig();
+        configManager.initialize(executor);
+        return configManager;
+    }
+
+    private AppSearchConfig() {
+    }
+
+    /**
+     * Initializes the {@link AppSearchConfig}
+     *
+     * <p>It fetches the custom properties from DeviceConfig if available.
+     *
+     * @param executor listener would be run on to handle P/H flag change.
+     */
+    private void initialize(@NonNull Executor executor) {
+        executor.execute(() -> {
+            // Attach the callback to get updates on those properties.
+            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APPSEARCH,
+                    executor,
+                    mOnDeviceConfigChangedListener);
+
+            DeviceConfig.Properties properties = DeviceConfig.getProperties(
+                    DeviceConfig.NAMESPACE_APPSEARCH, KEYS_TO_ALL_CACHED_VALUES);
+            updateCachedValues(properties);
+        });
+    }
+
+    // TODO(b/173532925) check this will be called. If we have a singleton instance for this
+    //  class, probably we don't need it.
+    @Override
+    public void close() {
+        synchronized (mLock) {
+            if (mIsClosedLocked) {
+                return;
+            }
+
+            DeviceConfig.removeOnPropertiesChangedListener(mOnDeviceConfigChangedListener);
+            mIsClosedLocked = true;
+        }
+    }
+
+    /** Returns cached value for minTimeIntervalBetweenSamplesMillis. */
+    public long getCachedMinTimeIntervalBetweenSamplesMillis() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getLong(KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                    DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
+        }
+    }
+
+    /**
+     * Returns cached value for default sampling interval for all the stats NOT listed in
+     * the configuration.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalDefault() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_DEFAULT, DEFAULT_SAMPLING_INTERVAL);
+        }
+    }
+
+    /**
+     * Returns cached value for sampling interval for batch calls.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForBatchCallStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
+    /**
+     * Returns cached value for sampling interval for putDocument.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForPutDocumentStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void throwIfClosedLocked() {
+        if (mIsClosedLocked) {
+            throw new IllegalStateException("Trying to use a closed AppSearchConfig instance.");
+        }
+    }
+
+    private void updateCachedValues(@NonNull DeviceConfig.Properties properties) {
+        for (String key : properties.getKeyset()) {
+            updateCachedValue(key, properties);
+        }
+    }
+
+    private void updateCachedValue(@NonNull String key,
+            @NonNull DeviceConfig.Properties properties) {
+        if (properties.getString(key, /*defaultValue=*/ null) == null) {
+            // Key is missing or value is just null. That is not expected if the key is
+            // defined in the configuration.
+            //
+            // We choose NOT to put the default value in the bundle.
+            // Instead, we let the getters handle what default value should be returned.
+            //
+            // Also we keep the old value in the bundle. So getters can still
+            // return last valid value.
+            return;
+        }
+
+        switch (key) {
+            case KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS:
+                synchronized (mLock) {
+                    mBundleLocked.putLong(key,
+                            properties.getLong(key,
+                                    DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS));
+                }
+                break;
+            case KEY_SAMPLING_INTERVAL_DEFAULT:
+            case KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS:
+            case KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS:
+                synchronized (mLock) {
+                    mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
+                }
+                break;
+            default:
+                break;
+        }
+    }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
index abcf161..7d0ce41 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -72,7 +72,7 @@
      *
      * <p> We can have correct extrapolated number by adding those counts back when we log
      * the same type of stats next time. E.g. the true count of an event could be estimated as:
-     * SUM(sampling_ratio * (num_skipped_sample + 1)) as est_count
+     * SUM(sampling_interval * (num_skipped_sample + 1)) as est_count
      *
      * <p>The key to the SparseArray is {@link CallStats.CallType}
      */
@@ -105,42 +105,42 @@
         // logging again.
         private final long mMinTimeIntervalBetweenSamplesMillis;
 
-        // Default sampling ratio for all types of stats
-        private final int mDefaultSamplingRatio;
+        // Default sampling interval for all types of stats
+        private final int mDefaultSamplingInterval;
 
         /**
-         * Sampling ratios for different types of stats
+         * Sampling intervals for different types of stats
          *
          * <p>This SparseArray is passed by client and is READ-ONLY. The key to that SparseArray is
          * {@link CallStats.CallType}
          *
-         * <p>If sampling ratio is missing for certain stats type,
-         * {@link Config#mDefaultSamplingRatio} will be used.
+         * <p>If sampling interval is missing for certain stats type,
+         * {@link Config#mDefaultSamplingInterval} will be used.
          *
-         * <p>E.g. sampling ratio=10 means that one out of every 10 stats was logged. If sampling
-         * ratio is 1, we will log each sample and it acts as if the sampling is disabled.
+         * <p>E.g. sampling interval=10 means that one out of every 10 stats was logged. If sampling
+         * interval is 1, we will log each sample and it acts as if the sampling is disabled.
          */
         @NonNull
-        private final SparseIntArray mSamplingRatios;
+        private final SparseIntArray mSamplingIntervals;
 
         /**
          * Configuration for {@link PlatformLogger}
          *
          * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
          *                                            required for two consecutive stats logged
-         * @param defaultSamplingRatio                default sampling ratio
-         * @param samplingRatios                      SparseArray to customize sampling ratio for
+         * @param defaultSamplingInterval             default sampling interval
+         * @param samplingIntervals                   SparseArray to customize sampling interval for
          *                                            different stat types
          */
         public Config(long minTimeIntervalBetweenSamplesMillis,
-                int defaultSamplingRatio,
-                @NonNull SparseIntArray samplingRatios) {
+                int defaultSamplingInterval,
+                @NonNull SparseIntArray samplingIntervals) {
             // TODO(b/173532925) Probably we can get rid of those three after we have p/h flags
             // for them.
-            // e.g. we can just call DeviceConfig.get(SAMPLING_RATIO_FOR_PUT_DOCUMENTS).
+            // e.g. we can just call DeviceConfig.get(SAMPLING_INTERVAL_FOR_PUT_DOCUMENTS).
             mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
-            mDefaultSamplingRatio = defaultSamplingRatio;
-            mSamplingRatios = samplingRatios;
+            mDefaultSamplingInterval = defaultSamplingInterval;
+            mSamplingIntervals = samplingIntervals;
         }
     }
 
@@ -150,14 +150,14 @@
     static final class ExtraStats {
         // UID for the calling package of the stats.
         final int mPackageUid;
-        // sampling ratio for the call type of the stats.
-        final int mSamplingRatio;
+        // sampling interval for the call type of the stats.
+        final int mSamplingInterval;
         // number of samplings skipped before the current one for the same call type.
         final int mSkippedSampleCount;
 
-        ExtraStats(int packageUid, int samplingRatio, int skippedSampleCount) {
+        ExtraStats(int packageUid, int samplingInterval, int skippedSampleCount) {
             mPackageUid = packageUid;
-            mSamplingRatio = samplingRatio;
+            mSamplingInterval = samplingInterval;
             mSkippedSampleCount = skippedSampleCount;
         }
     }
@@ -224,7 +224,7 @@
      *
      * @return removed UID for the package, or {@code INVALID_UID} if package was not previously
      * cached.
-    */
+     */
     public int removeCachedUidForPackage(@NonNull String packageName) {
         // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
         Objects.requireNonNull(packageName);
@@ -243,7 +243,7 @@
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_CALL_STATS_REPORTED,
-                    extraStats.mSamplingRatio,
+                    extraStats.mSamplingInterval,
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
@@ -275,7 +275,7 @@
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_PUT_DOCUMENT_STATS_REPORTED,
-                    extraStats.mSamplingRatio,
+                    extraStats.mSamplingInterval,
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
@@ -312,7 +312,7 @@
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_QUERY_STATS_REPORTED,
-                    extraStats.mSamplingRatio,
+                    extraStats.mSamplingInterval,
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
@@ -355,7 +355,7 @@
         ExtraStats extraStats = createExtraStatsLocked(/*packageName=*/ null,
                 CallStats.CALL_TYPE_INITIALIZE);
         AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_INITIALIZE_STATS_REPORTED,
-                extraStats.mSamplingRatio,
+                extraStats.mSamplingInterval,
                 extraStats.mSkippedSampleCount,
                 extraStats.mPackageUid,
                 stats.getStatusCode(),
@@ -428,14 +428,14 @@
             packageUid = getPackageUidAsUserLocked(packageName);
         }
 
-        int samplingRatio = mConfig.mSamplingRatios.get(callType,
-                mConfig.mDefaultSamplingRatio);
+        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
+                mConfig.mDefaultSamplingInterval);
 
         int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
                 /*valueOfKeyIfNotFound=*/ 0);
         mSkippedSampleCountLocked.put(callType, 0);
 
-        return new ExtraStats(packageUid, samplingRatio, skippedSampleCount);
+        return new ExtraStats(packageUid, samplingInterval, skippedSampleCount);
     }
 
     /**
@@ -450,11 +450,11 @@
     // rate limiting.
     @VisibleForTesting
     boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
-        int samplingRatio = mConfig.mSamplingRatios.get(callType,
-                mConfig.mDefaultSamplingRatio);
+        int samplingInterval = mConfig.mSamplingIntervals.get(callType,
+                mConfig.mDefaultSamplingInterval);
 
         // Sampling
-        if (!shouldSample(samplingRatio)) {
+        if (!shouldSample(samplingInterval)) {
             return false;
         }
 
@@ -475,15 +475,15 @@
     /**
      * Checks if the stats should be "sampled"
      *
-     * @param samplingRatio sampling ratio
+     * @param samplingInterval sampling interval
      * @return if the stats should be sampled
      */
-    private boolean shouldSample(int samplingRatio) {
-        if (samplingRatio <= 0) {
+    private boolean shouldSample(int samplingInterval) {
+        if (samplingInterval <= 0) {
             return false;
         }
 
-        return mRng.nextInt((int) samplingRatio) == 0;
+        return mRng.nextInt((int) samplingInterval) == 0;
     }
 
     /**
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d31679e..2bae190 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -201,6 +201,11 @@
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int);
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int);
     method public static int strOpToOp(@NonNull String);
+    field public static final int ATTRIBUTION_CHAIN_ID_NONE = -1; // 0xffffffff
+    field public static final int ATTRIBUTION_FLAGS_NONE = 0; // 0x0
+    field public static final int ATTRIBUTION_FLAG_ACCESSOR = 1; // 0x1
+    field public static final int ATTRIBUTION_FLAG_INTERMEDIARY = 2; // 0x2
+    field public static final int ATTRIBUTION_FLAG_RECEIVER = 4; // 0x4
     field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
     field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
     field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
@@ -230,6 +235,10 @@
     method public void offsetBeginAndEndTime(long);
   }
 
+  public static interface AppOpsManager.OnOpActiveChangedListener {
+    method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
+  }
+
   public class BroadcastOptions {
     ctor public BroadcastOptions(@NonNull android.os.Bundle);
     method public int getMaxManifestReceiverApiLevel();
@@ -669,7 +678,7 @@
 
   public final class AttributionSource implements android.os.Parcelable {
     ctor public AttributionSource(int, @Nullable String, @Nullable String);
-    ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable android.content.AttributionSource);
+    ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
   }
 
@@ -2041,7 +2050,7 @@
   public final class PermissionManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
-    method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
+    method public void registerAttributionSource(@NonNull android.content.AttributionSource);
   }
 
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1415212..eb14c11 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -708,6 +708,62 @@
         }
     }
 
+    /**
+     * Attribution chain flag: specifies that this is the accessor. When
+     * an app A accesses the data that is then passed to app B that is then
+     * passed to C, we call app A accessor, app B intermediary, and app C
+     * receiver. If A accesses the data for itself, then it is the accessor
+     * and the receiver.
+     * @hide
+     */
+    @TestApi
+    public static final int ATTRIBUTION_FLAG_ACCESSOR = 0x1;
+
+    /**
+     * Attribution chain flag: specifies that this is the intermediary. When
+     * an app A accesses the data that is then passed to app B that is then
+     * passed to C, we call app A accessor, app B intermediary, and app C
+     * receiver. If A accesses the data for itself, then it is the accessor
+     * and the receiver.
+     * @hide
+     */
+    @TestApi
+    public static final int ATTRIBUTION_FLAG_INTERMEDIARY = 0x2;
+
+    /**
+     * Attribution chain flag: specifies that this is the receiver. When
+     * an app A accesses the data that is then passed to app B that is then
+     * passed to C, we call app A accessor, app B intermediary, and app C
+     * receiver. If A accesses the data for itself, then it is the accessor
+     * and the receiver.
+     * @hide
+     */
+    @TestApi
+    public static final int ATTRIBUTION_FLAG_RECEIVER = 0x4;
+
+    /**
+     * No attribution flags.
+     * @hide
+     */
+    @TestApi
+    public static final int ATTRIBUTION_FLAGS_NONE = 0x0;
+
+    /**
+     * No attribution chain id.
+     * @hide
+     */
+    @TestApi
+    public static final int ATTRIBUTION_CHAIN_ID_NONE = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            ATTRIBUTION_FLAG_ACCESSOR,
+            ATTRIBUTION_FLAG_INTERMEDIARY,
+            ATTRIBUTION_FLAG_RECEIVER
+    })
+    public @interface AttributionFlags {}
+
     // These constants are redefined here to work around a metalava limitation/bug where
     // @IntDef is not able to see @hide symbols when they are hidden via package hiding:
     // frameworks/base/core/java/com/android/internal/package.html
@@ -7063,6 +7119,25 @@
          */
         void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
                 boolean active);
+
+        /**
+         * Called when the active state of an app-op changes.
+         *
+         * @param op The operation that changed.
+         * @param uid The UID performing the operation.
+         * @param packageName The package performing the operation.
+         * @param attributionTag The operation's attribution tag.
+         * @param active Whether the operation became active or inactive.
+         * @param attributionFlags the attribution flags for this operation.
+         * @param attributionChainId the unique id of the attribution chain this op is a part of.
+         * @hide
+         */
+        @TestApi
+        default void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
+                @Nullable String attributionTag, boolean active, @AttributionFlags
+                int attributionFlags, int attributionChainId) {
+            onOpActiveChanged(op, uid, packageName, active);
+        }
     }
 
     /**
@@ -7684,14 +7759,17 @@
             }
             cb = new IAppOpsActiveCallback.Stub() {
                 @Override
-                public void opActiveChanged(int op, int uid, String packageName, boolean active) {
+                public void opActiveChanged(int op, int uid, String packageName,
+                        String attributionTag, boolean active, @AttributionFlags
+                        int attributionFlags, int attributionChainId) {
                     executor.execute(() -> {
                         if (callback instanceof OnOpActiveChangedInternalListener) {
                             ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
                                     uid, packageName, active);
                         }
                         if (sOpToString[op] != null) {
-                            callback.onOpActiveChanged(sOpToString[op], uid, packageName, active);
+                            callback.onOpActiveChanged(sOpToString[op], uid, packageName,
+                                    attributionTag, active, attributionFlags, attributionChainId);
                         }
                     });
                 }
@@ -8169,8 +8247,9 @@
     public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
-                message, /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), message,
+                        /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8255,8 +8334,8 @@
             int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
                 mContext.getAttributionSource(), new AttributionSource(proxiedUid,
-                        proxiedPackageName, proxiedAttributionTag)), message,
-                        /*skipProxyOperation*/ false);
+                        proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
+                        .getToken())), message,/*skipProxyOperation*/ false);
     }
 
     /**
@@ -8592,6 +8671,29 @@
      */
     public int startOpNoThrow(int op, int uid, @NonNull String packageName,
             boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) {
+        return startOpNoThrow(mContext.getAttributionSource().getToken(), op, uid, packageName,
+                startIfModeDefault, attributionTag, message);
+    }
+
+    /**
+     * @see #startOpNoThrow(String, int, String, String, String)
+     *
+     * @hide
+     */
+    public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
+            boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message) {
+        return startOpNoThrow(token, op, uid, packageName, startIfModeDefault, attributionTag,
+                message, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+    }
+
+    /**
+     * @see #startOpNoThrow(String, int, String, String, String)
+     *
+     * @hide
+     */
+    public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
+            boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
         try {
             collectNoteOpCallsForValidation(op);
             int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
@@ -8604,9 +8706,9 @@
                 }
             }
 
-            SyncNotedAppOp syncOp = mService.startOperation(getClientId(), op, uid, packageName,
+            SyncNotedAppOp syncOp = mService.startOperation(token, op, uid, packageName,
                     attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message,
-                    shouldCollectMessage);
+                    shouldCollectMessage, attributionFlags, attributionChainId);
 
             if (syncOp.getOpMode() == MODE_ALLOWED) {
                 if (collectionMode == COLLECT_SELF) {
@@ -8643,8 +8745,9 @@
     public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)),
-                message, /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), message,
+                        /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8690,8 +8793,9 @@
             @Nullable String message) {
         return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
                 mContext.getAttributionSource(), new AttributionSource(proxiedUid,
-                        proxiedPackageName, proxiedAttributionTag)), message,
-                /*skipProxyOperation*/ false);
+                        proxiedPackageName, proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), message,
+                        /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8705,6 +8809,23 @@
      */
     public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean skipProxyOperation) {
+        return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation,
+                ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+    }
+
+    /**
+     * Like {@link #startProxyOp(String, AttributionSource, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED} and
+     * the checks is for the attribution chain specified by the {@link AttributionSource}.
+     *
+     * @see #startProxyOp(String, AttributionSource, String)
+     *
+     * @hide
+     */
+    public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+            @Nullable String message, boolean skipProxyOperation, @AttributionFlags
+            int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+            int attributionChainId) {
         try {
             collectNoteOpCallsForValidation(op);
             int collectionMode = getNotedOpCollectionMode(
@@ -8719,9 +8840,10 @@
                 }
             }
 
-            SyncNotedAppOp syncOp = mService.startProxyOperation(getClientId(), op,
+            SyncNotedAppOp syncOp = mService.startProxyOperation(op,
                     attributionSource, false, collectionMode == COLLECT_ASYNC, message,
-                    shouldCollectMessage, skipProxyOperation);
+                    shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                    proxiedAttributionFlags, attributionChainId);
 
             if (syncOp.getOpMode() == MODE_ALLOWED) {
                 if (collectionMode == COLLECT_SELF) {
@@ -8785,8 +8907,18 @@
      */
     public void finishOp(int op, int uid, @NonNull String packageName,
             @Nullable String attributionTag) {
+        finishOp(mContext.getAttributionSource().getToken(), op, uid, packageName, attributionTag);
+    }
+
+    /**
+     * @see #finishOp(String, int, String, String)
+     *
+     * @hide
+     */
+    public void finishOp(IBinder token, int op, int uid, @NonNull String packageName,
+            @Nullable String attributionTag) {
         try {
-            mService.finishOperation(getClientId(), op, uid, packageName, attributionTag);
+            mService.finishOperation(token, op, uid, packageName, attributionTag);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -8807,23 +8939,26 @@
     public void finishProxyOp(@NonNull String op, int proxiedUid,
             @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
         finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag)));
+                new AttributionSource(proxiedUid, proxiedPackageName,  proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
     }
 
     /**
      * Report that an application is no longer performing an operation that had previously
-     * been started with {@link #startProxyOp(String, AttributionSource, String)}. There is no
-     * validation of input or result; the parameters supplied here must be the exact same ones
-     * previously passed in when starting the operation.
+     * been started with {@link #startProxyOp(String, AttributionSource, String, boolean)}. There
+     * is no validation of input or result; the parameters supplied here must be the exact same
+     * ones previously passed in when starting the operation.
      *
      * @param op The operation which was started
      * @param attributionSource The permission identity for which to finish
+     * @param skipProxyOperation Whether to skip the proxy finish.
      *
      * @hide
      */
-    public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource) {
+    public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
+            boolean skipProxyOperation) {
         try {
-            mService.finishProxyOperation(getClientId(), strOpToOp(op), attributionSource);
+            mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 2de0ddb..a757e32 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.app.AppOpsManager.AttributionFlags;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.AttributionSource;
@@ -24,13 +25,13 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.NonaFunction;
-import com.android.internal.util.function.OctFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintFunction;
 import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
 
 /**
  * App ops service local interface.
@@ -52,8 +53,8 @@
          * @return The app op check result.
          */
         int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag,
-                boolean raw,
-                QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl);
+                boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer>
+                superImpl);
 
         /**
          * Allows overriding check audio operation behavior.
@@ -116,20 +117,22 @@
          * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
          * @param message The message in the async noted op
          * @param shouldCollectMessage whether to collect messages
+         * @param attributionFlags the attribution flags for this operation.
+         * @param attributionChainId the unique id of the attribution chain this op is a part of.
          * @param superImpl The super implementation.
          * @return The app op note result.
          */
         SyncNotedAppOp startOperation(IBinder token, int code, int uid,
                 @Nullable String packageName, @Nullable String attributionTag,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
-                @Nullable String message, boolean shouldCollectMessage, @NonNull NonaFunction<
-                        IBinder, Integer, Integer, String, String, Boolean, Boolean, String,
-                        Boolean, SyncNotedAppOp> superImpl);
+                @Nullable String message, boolean shouldCollectMessage,
+                @AttributionFlags int attributionFlags, int attributionChainId,
+                @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
+                        Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl);
 
         /**
          * Allows overriding start proxy operation behavior.
          *
-         * @param token The client state.
          * @param code The op code to start.
          * @param attributionSource The permission identity of the caller.
          * @param startIfModeDefault Whether to start the op of the mode is default.
@@ -137,26 +140,29 @@
          * @param message The message in the async noted op
          * @param shouldCollectMessage whether to collect messages
          * @param skipProxyOperation Whether to skip the proxy portion of the operation
+         * @param proxyAttributionFlags The attribution flags for the proxy.
+         * @param proxiedAttributionFlags The attribution flags for the proxied.
+         * @oaram attributionChainId The id of the attribution chain this operation is a part of.
          * @param superImpl The super implementation.
          * @return The app op note result.
          */
-        SyncNotedAppOp startProxyOperation(IBinder token, int code,
-                @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
-                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-                boolean skipProxyOperation, @NonNull OctFunction<IBinder, Integer,
-                        AttributionSource, Boolean, Boolean, String, Boolean, Boolean,
+        SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource,
+                boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
+                boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
+                int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+                int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean,
+                        Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
                         SyncNotedAppOp> superImpl);
 
         /**
          * Allows overriding finish proxy op.
          *
-         * @param clientId Client state token.
          * @param code The op code to finish.
          * @param attributionSource The permission identity of the caller.
          */
-        void finishProxyOperation(IBinder clientId, int code,
-                @NonNull AttributionSource attributionSource,
-                @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl);
+        void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+                boolean skipProxyOperation,
+                @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl);
     }
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index daef8b1..16b6ea5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3157,13 +3157,7 @@
         // If we want to access protected data on behalf of another app we need to
         // tell the OS that we opt in to participate in the attribution chain.
         if (nextAttributionSource != null) {
-            // If an app happened to stub the internal OS for testing the registration method
-            // can return null. In this case we keep the current untrusted attribution source.
-            final AttributionSource registeredAttributionSource = getSystemService(
-                    PermissionManager.class).registerAttributionSource(attributionSource);
-            if (registeredAttributionSource != null) {
-                return registeredAttributionSource;
-            }
+            getSystemService(PermissionManager.class).registerAttributionSource(attributionSource);
         }
         return attributionSource;
     }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 91dad2a..871d48b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@
 import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.permission.LegacyPermissionManager;
+import android.permission.PermissionCheckerManager;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
 import android.print.IPrintManager;
@@ -1334,6 +1335,14 @@
                                 ctx.getMainThreadHandler());
                     }});
 
+        registerService(Context.PERMISSION_CHECKER_SERVICE, PermissionCheckerManager.class,
+                new CachedServiceFetcher<PermissionCheckerManager>() {
+                    @Override
+                    public PermissionCheckerManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        return new PermissionCheckerManager(ctx.getOuterContext());
+                    }});
+
         registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class,
                 new CachedServiceFetcher<DynamicSystemManager>() {
                     @Override
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 7ab731f..1dda637 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -21,6 +21,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -94,14 +95,22 @@
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag) {
-        this(uid, packageName, attributionTag, /*next*/ null);
+        this(uid, packageName, attributionTag, new Binder());
     }
 
     /** @hide */
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
-            @Nullable String attributionTag, @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next);
+            @Nullable String attributionTag, @NonNull IBinder token) {
+        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+                /*next*/ null);
+    }
+
+    /** @hide */
+    public AttributionSource(int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @NonNull IBinder token,
+            @Nullable AttributionSource next) {
+        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
     }
 
     /** @hide */
@@ -109,28 +118,32 @@
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
             @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null)
+        this(uid, packageName, attributionTag, (renouncedPermissions != null)
                 ? renouncedPermissions.toArray(new String[0]) : null, next);
     }
 
     /** @hide */
-    public AttributionSource(@NonNull AttributionSource current,
-            @Nullable AttributionSource next) {
+    public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
         this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
-                /*token*/ null, /*renouncedPermissions*/ null, next);
+                current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
     }
 
     AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
-            @Nullable IBinder token, @Nullable String[] renouncedPermissions,
+            @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
+        this(uid, packageName, attributionTag, new Binder(), renouncedPermissions, next);
+    }
+
+    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+            @NonNull IBinder token, @Nullable String[] renouncedPermissions,
             @Nullable AttributionSource next) {
         mAttributionSourceState = new AttributionSourceState();
         mAttributionSourceState.uid = uid;
+        mAttributionSourceState.token = token;
         mAttributionSourceState.packageName = packageName;
         mAttributionSourceState.attributionTag = attributionTag;
-        mAttributionSourceState.token = token;
         mAttributionSourceState.renouncedPermissions = renouncedPermissions;
         mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
-                {next.mAttributionSourceState} : null;
+                {next.mAttributionSourceState} : new AttributionSourceState[0];
     }
 
     AttributionSource(@NonNull Parcel in) {
@@ -145,18 +158,12 @@
     /** @hide */
     public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
         return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
-                getToken(), mAttributionSourceState.renouncedPermissions, next);
-    }
-
-    /** @hide */
-    public AttributionSource withToken(@Nullable IBinder token) {
-        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
-                token, mAttributionSourceState.renouncedPermissions, getNext());
+                mAttributionSourceState.renouncedPermissions, next);
     }
 
     /** @hide */
     public AttributionSource withPackageName(@Nullable String packageName) {
-        return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(),
+        return new AttributionSource(getUid(), packageName, getAttributionTag(),
                 mAttributionSourceState.renouncedPermissions, getNext());
     }
 
@@ -165,6 +172,45 @@
         return mAttributionSourceState;
     }
 
+    /** @hide */
+    public @NonNull ScopedParcelState asScopedParcelState() {
+        return new ScopedParcelState(this);
+    }
+
+    /** @hide */
+    public static AttributionSource myAttributionSource() {
+        return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(),
+                /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null);
+    }
+
+    /**
+     * This is a scoped object that exposes the content of an attribution source
+     * as a parcel. This is useful when passing one to native and avoid custom
+     * conversion logic from Java to native state that needs to be kept in sync
+     * as attribution source evolves. This way we use the same logic for passing
+     * to native as the ones for passing in an IPC - in both cases this is the
+     * same auto generated code.
+     *
+     * @hide
+     */
+    public static class ScopedParcelState implements AutoCloseable {
+        private final Parcel mParcel;
+
+        public @NonNull Parcel getParcel() {
+            return mParcel;
+        }
+
+        public ScopedParcelState(AttributionSource attributionSource) {
+            mParcel = Parcel.obtain();
+            attributionSource.writeToParcel(mParcel, 0);
+            mParcel.setDataPosition(0);
+        }
+
+        public void close() {
+            mParcel.recycle();
+        }
+    }
+
     /**
      * If you are handling an IPC and you don't trust the caller you need to validate
      * whether the attribution source is one for the calling app to prevent the caller
@@ -209,7 +255,8 @@
                     "attributionTag = " + mAttributionSourceState.attributionTag + ", " +
                     "token = " + mAttributionSourceState.token + ", " +
                     "next = " + (mAttributionSourceState.next != null
-                            ? mAttributionSourceState.next[0]: null) +
+                                    && mAttributionSourceState.next.length > 0
+                            ? mAttributionSourceState.next[0] : null) +
                     " }";
         }
         return super.toString();
@@ -221,7 +268,8 @@
      * @hide
      */
     public int getNextUid() {
-        if (mAttributionSourceState.next != null) {
+        if (mAttributionSourceState.next != null
+                && mAttributionSourceState.next.length > 0) {
             return mAttributionSourceState.next[0].uid;
         }
         return Process.INVALID_UID;
@@ -233,26 +281,42 @@
      * @hide
      */
     public @Nullable String getNextPackageName() {
-        if (mAttributionSourceState.next != null) {
+        if (mAttributionSourceState.next != null
+                && mAttributionSourceState.next.length > 0) {
             return mAttributionSourceState.next[0].packageName;
         }
         return null;
     }
 
     /**
-     * @return The nexxt package's attribution tag that would receive
+     * @return The next package's attribution tag that would receive
      * the permission protected data.
      *
      * @hide
      */
     public @Nullable String getNextAttributionTag() {
-        if (mAttributionSourceState.next != null) {
+        if (mAttributionSourceState.next != null
+                && mAttributionSourceState.next.length > 0) {
             return mAttributionSourceState.next[0].attributionTag;
         }
         return null;
     }
 
     /**
+     * @return The next package's token that would receive
+     * the permission protected data.
+     *
+     * @hide
+     */
+    public @Nullable IBinder getNextToken() {
+        if (mAttributionSourceState.next != null
+                && mAttributionSourceState.next.length > 0) {
+            return mAttributionSourceState.next[0].token;
+        }
+        return null;
+    }
+
+    /**
      * Checks whether this attribution source can be trusted. That is whether
      * the app it refers to created it and provided to the attribution chain.
      *
@@ -311,7 +375,7 @@
      *
      * @hide
      */
-    public @Nullable IBinder getToken() {
+    public @NonNull IBinder getToken() {
         return mAttributionSourceState.token;
     }
 
@@ -319,7 +383,8 @@
      * The next app to receive the permission protected data.
      */
     public @Nullable AttributionSource getNext() {
-        if (mNextCached == null && mAttributionSourceState.next != null) {
+        if (mNextCached == null && mAttributionSourceState.next != null
+                && mAttributionSourceState.next.length > 0) {
             mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
         }
         return mNextCached;
@@ -442,7 +507,7 @@
         @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
         public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
+            mBuilderFieldsSet |= 0x8;
             mAttributionSourceState.renouncedPermissions = (value != null)
                     ? value.toArray(new String[0]) : null;
             return this;
@@ -453,9 +518,9 @@
          */
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
+            mBuilderFieldsSet |= 0x10;
             mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
-                    {value.mAttributionSourceState} : null;
+                    {value.mAttributionSourceState} : mAttributionSourceState.next;
             return this;
         }
 
@@ -471,14 +536,16 @@
                 mAttributionSourceState.attributionTag = null;
             }
             if ((mBuilderFieldsSet & 0x8) == 0) {
-                mAttributionSourceState.token = null;
-            }
-            if ((mBuilderFieldsSet & 0x10) == 0) {
                 mAttributionSourceState.renouncedPermissions = null;
             }
-            if ((mBuilderFieldsSet & 0x20) == 0) {
+            if ((mBuilderFieldsSet & 0x10) == 0) {
                 mAttributionSourceState.next = null;
             }
+            mAttributionSourceState.token = new Binder();
+            if (mAttributionSourceState.next == null) {
+                // The NDK aidl backend doesn't support null parcelable arrays.
+                mAttributionSourceState.next = new AttributionSourceState[0];
+            }
             return new AttributionSource(mAttributionSourceState);
         }
 
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 88686a3..dc29c5e 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -49,6 +49,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.permission.PermissionCheckerManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -670,7 +671,7 @@
             }
         }
 
-        @PermissionChecker.PermissionResult
+        @PermissionCheckerManager.PermissionResult
         private void enforceFilePermission(@NonNull AttributionSource attributionSource,
                 Uri uri, String mode)
                 throws FileNotFoundException, SecurityException {
@@ -687,7 +688,7 @@
             }
         }
 
-        @PermissionChecker.PermissionResult
+        @PermissionCheckerManager.PermissionResult
         private int enforceReadPermission(@NonNull AttributionSource attributionSource, Uri uri)
                 throws SecurityException {
             final int result = enforceReadPermissionInner(uri, attributionSource);
@@ -705,7 +706,7 @@
             return PermissionChecker.PERMISSION_GRANTED;
         }
 
-        @PermissionChecker.PermissionResult
+        @PermissionCheckerManager.PermissionResult
         private int enforceWritePermission(@NonNull AttributionSource attributionSource, Uri uri)
                 throws SecurityException {
             final int result = enforceWritePermissionInner(uri, attributionSource);
@@ -738,7 +739,7 @@
      * Verify that calling app holds both the given permission and any app-op
      * associated with that permission.
      */
-    @PermissionChecker.PermissionResult
+    @PermissionCheckerManager.PermissionResult
     private int checkPermission(String permission,
             @NonNull AttributionSource attributionSource) {
         if (Binder.getCallingPid() == Process.myPid()) {
@@ -753,7 +754,7 @@
     }
 
     /** {@hide} */
-    @PermissionChecker.PermissionResult
+    @PermissionCheckerManager.PermissionResult
     protected int enforceReadPermissionInner(Uri uri,
             @NonNull AttributionSource attributionSource) throws SecurityException {
         final Context context = getContext();
@@ -836,7 +837,7 @@
     }
 
     /** {@hide} */
-    @PermissionChecker.PermissionResult
+    @PermissionCheckerManager.PermissionResult
     protected int enforceWritePermissionInner(Uri uri,
             @NonNull AttributionSource attributionSource) throws SecurityException {
         final Context context = getContext();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 886cd7f..ea0e321 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4836,6 +4836,14 @@
     public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
 
     /**
+     * Official published name of the (internal) permission checker service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String PERMISSION_CHECKER_SERVICE = "permission_checker";
+
+    /**
      * Use with {@link #getSystemService(String) to retrieve an
      * {@link android.apphibernation.AppHibernationManager}} for
      * communicating with the hibernation service.
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 66e0883..a167cb3 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,19 +16,14 @@
 
 package android.content;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.os.Binder;
-import android.os.IBinder;
 import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.permission.IPermissionChecker;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionCheckerManager.PermissionResult;
 
 /**
  * This class provides permission check APIs that verify both the
@@ -75,7 +70,7 @@
      *
      * @hide
      */
-    public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
+    public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED;
 
     /**
      * The permission is denied. Applicable only to runtime and app op permissions.
@@ -89,7 +84,8 @@
      *
      * @hide
      */
-    public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+    public static final int PERMISSION_SOFT_DENIED =
+            PermissionCheckerManager.PERMISSION_SOFT_DENIED;
 
     /**
      * The permission is denied.
@@ -103,18 +99,12 @@
      *
      * @hide
      */
-    public static final int PERMISSION_HARD_DENIED =  IPermissionChecker.PERMISSION_HARD_DENIED;
+    public static final int PERMISSION_HARD_DENIED =
+            PermissionCheckerManager.PERMISSION_HARD_DENIED;
 
     /** Constant when the PID for which we check permissions is unknown. */
     public static final int PID_UNKNOWN = -1;
 
-    /** @hide */
-    @IntDef({PERMISSION_GRANTED,
-            PERMISSION_SOFT_DENIED,
-            PERMISSION_HARD_DENIED})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PermissionResult {}
-
     private static volatile IPermissionChecker sService;
 
     private PermissionChecker() {
@@ -157,7 +147,7 @@
      *
      * @see #checkPermissionForPreflight(Context, String, int, int, String)
      */
-    @PermissionResult
+    @PermissionCheckerManager.PermissionResult
     public static int checkPermissionForDataDelivery(@NonNull Context context,
             @NonNull String permission, int pid, int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
@@ -321,19 +311,13 @@
                 message, startDataDelivery, /*fromDatasource*/ false);
     }
 
+    @SuppressWarnings("ConstantConditions")
     private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
             @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.
-        try {
-            final int result = getPermissionCheckerService().checkPermission(permission,
-                    attributionSource.asState(), message, true /*forDataDelivery*/,
-                    startDataDelivery, fromDatasource);
-            return result;
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        return PERMISSION_HARD_DENIED;
+        return context.getSystemService(PermissionCheckerManager.class).checkPermission(permission,
+                attributionSource.asState(), message, true /*forDataDelivery*/, startDataDelivery,
+                fromDatasource, AppOpsManager.OP_NONE);
     }
 
     /**
@@ -365,17 +349,13 @@
      * @see #checkPermissionForPreflight(Context, String, AttributionSource)
      */
     @PermissionResult
+    @SuppressWarnings("ConstantConditions")
     public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
             @NonNull String permission, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        try {
-            return getPermissionCheckerService().checkPermission(permission,
-                    attributionSource.asState(), message, true /*forDataDelivery*/,
-                    /*startDataDelivery*/ true, /*fromDatasource*/ false);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        return PERMISSION_HARD_DENIED;
+        return context.getSystemService(PermissionCheckerManager.class).checkPermission(
+                permission, attributionSource.asState(), message, true /*forDataDelivery*/,
+                /*startDataDelivery*/ true, /*fromDatasource*/ false, AppOpsManager.OP_NONE);
     }
 
     /**
@@ -404,17 +384,13 @@
      * @see #finishDataDelivery(Context, String, AttributionSource)
      */
     @PermissionResult
+    @SuppressWarnings("ConstantConditions")
     public static int startOpForDataDelivery(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        try {
-            return getPermissionCheckerService().checkOp(
-                    AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
-                    true /*forDataDelivery*/, true /*startDataDelivery*/);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        return PERMISSION_HARD_DENIED;
+        return context.getSystemService(PermissionCheckerManager.class).checkOp(
+                AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+                true /*forDataDelivery*/, true /*startDataDelivery*/);
     }
 
     /**
@@ -428,13 +404,32 @@
      * @see #startOpForDataDelivery(Context, String, AttributionSource, String)
      * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
      */
+    @SuppressWarnings("ConstantConditions")
     public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
             @NonNull AttributionSource attributionSource) {
-        try {
-            getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState());
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
+        context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+                AppOpsManager.strOpToOp(op), attributionSource.asState(),
+                /*fromDatasource*/ false);
+    }
+
+    /**
+     * Finishes an ongoing op for data access chain described by the given {@link
+     * AttributionSource}. Call this method if you are the datasource which would
+     * not finish an op for your attribution source as it was not started.
+     *
+     * @param context Context for accessing resources.
+     * @param op The op to finish.
+     * @param attributionSource The identity for which finish op.
+     *
+     * @see #startOpForDataDelivery(Context, String, AttributionSource, String)
+     * @see #checkPermissionAndStartDataDelivery(Context, String, AttributionSource, String)
+     */
+    @SuppressWarnings("ConstantConditions")
+    public static void finishDataDeliveryFromDatasource(@NonNull Context context,
+            @NonNull String op, @NonNull AttributionSource attributionSource) {
+        context.getSystemService(PermissionCheckerManager.class).finishDataDelivery(
+                AppOpsManager.strOpToOp(op), attributionSource.asState(),
+                /*fromDatasource*/ true);
     }
 
     /**
@@ -466,17 +461,13 @@
      * @see #checkOpForDataDelivery(Context, String, AttributionSource, String)
      */
     @PermissionResult
+    @SuppressWarnings("ConstantConditions")
     public static int checkOpForPreflight(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        try {
-            return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
-                    attributionSource.asState(), message, false /*forDataDelivery*/,
-                    false /*startDataDelivery*/);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        return PERMISSION_HARD_DENIED;
+        return context.getSystemService(PermissionCheckerManager.class).checkOp(
+                AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+                false /*forDataDelivery*/, false /*startDataDelivery*/);
     }
 
     /**
@@ -505,17 +496,13 @@
      * @see #checkOpForPreflight(Context, String, AttributionSource, String)
      */
     @PermissionResult
+    @SuppressWarnings("ConstantConditions")
     public static int checkOpForDataDelivery(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        try {
-            return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
-                    attributionSource.asState(), message, true /*forDataDelivery*/,
-                    false /*startDataDelivery*/);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        return PERMISSION_HARD_DENIED;
+        return context.getSystemService(PermissionCheckerManager.class).checkOp(
+                AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+                true /*forDataDelivery*/, false /*startDataDelivery*/);
     }
 
     /**
@@ -584,16 +571,13 @@
      *     String, boolean)
      */
     @PermissionResult
+    @SuppressWarnings("ConstantConditions")
     public static int checkPermissionForPreflight(@NonNull Context context,
             @NonNull String permission, @NonNull AttributionSource attributionSource) {
-        try {
-            return getPermissionCheckerService().checkPermission(permission,
-                    attributionSource.asState(), null /*message*/, false /*forDataDelivery*/,
-                    /*startDataDelivery*/ false, /*fromDatasource*/ false);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        return PERMISSION_HARD_DENIED;
+        return context.getSystemService(PermissionCheckerManager.class)
+                .checkPermission(permission, attributionSource.asState(), null /*message*/,
+                false /*forDataDelivery*/, /*startDataDelivery*/ false, /*fromDatasource*/ false,
+                AppOpsManager.OP_NONE);
     }
 
     /**
@@ -827,13 +811,4 @@
         return checkPermissionForPreflight(context, permission, Binder.getCallingPid(),
                 Binder.getCallingUid(), packageName);
     }
-
-    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);
-        }
-        return sService;
-    }
 }
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index ef075e1..9ab6955 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -87,7 +87,7 @@
 
     boolean isAutoRevokeExempted(String packageName, int userId);
 
-    AttributionSource registerAttributionSource(in AttributionSource source);
+    void registerAttributionSource(in AttributionSource source);
 
     boolean isRegisteredAttributionSource(in AttributionSource source);
 }
diff --git a/core/java/android/permission/PermissionCheckerManager.java b/core/java/android/permission/PermissionCheckerManager.java
new file mode 100644
index 0000000..7523816
--- /dev/null
+++ b/core/java/android/permission/PermissionCheckerManager.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.AttributionSourceState;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Manager for checking runtime and app op permissions. This is a temporary
+ * class and we may fold its function in the PermissionManager once the
+ * permission re-architecture starts falling into place. The main benefit
+ * of this class is to allow context level caching.
+ *
+ * @hide
+ */
+public class PermissionCheckerManager {
+
+    /**
+     * The permission is granted.
+     */
+    public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
+
+    /**
+     * The permission is denied. Applicable only to runtime and app op permissions.
+     *
+     * <p>Returned when:
+     * <ul>
+     *   <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>
+     */
+    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>
+     */
+    public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
+
+    /** @hide */
+    @IntDef({PERMISSION_GRANTED,
+            PERMISSION_SOFT_DENIED,
+            PERMISSION_HARD_DENIED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PermissionResult {}
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final IPermissionChecker mService;
+
+    @NonNull
+    private final PackageManager mPackageManager;
+
+    public PermissionCheckerManager(@NonNull Context context)
+            throws ServiceManager.ServiceNotFoundException {
+        mContext = context;
+        mService = IPermissionChecker.Stub.asInterface(ServiceManager.getServiceOrThrow(
+                Context.PERMISSION_CHECKER_SERVICE));
+        mPackageManager = context.getPackageManager();
+    }
+
+    /**
+     * Checks a permission by validating the entire attribution source chain. If the
+     * permission is associated with an app op the op is also noted/started for the
+     * entire attribution chain.
+     *
+     * @param permission The permission
+     * @param attributionSource The attribution chain to check.
+     * @param message Message associated with the permission if permission has an app op
+     * @param forDataDelivery Whether the check is for delivering data if permission has an app op
+     * @param startDataDelivery Whether to start data delivery (start op) if permission has
+     *     an app op
+     * @param fromDatasource Whether the check is by a datasource (skip checks for the
+     *     first attribution source in the chain as this is the datasource)
+     * @param attributedOp Alternative app op to attribute
+     * @return The permission check result.
+     */
+    @PermissionResult
+    public int checkPermission(@NonNull String permission,
+            @NonNull AttributionSourceState attributionSource, @Nullable String message,
+            boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
+            int attributedOp) {
+        Objects.requireNonNull(permission);
+        Objects.requireNonNull(attributionSource);
+        // Fast path for non-runtime, non-op permissions where the attribution chain has
+        // length one. This is the majority of the cases and we want these to be fast by
+        // hitting the local in process permission cache.
+        if (AppOpsManager.permissionToOpCode(permission) == AppOpsManager.OP_NONE) {
+            if (fromDatasource) {
+                if (attributionSource.next != null && attributionSource.next.length > 0) {
+                    return mContext.checkPermission(permission, attributionSource.next[0].pid,
+                            attributionSource.next[0].uid) == PackageManager.PERMISSION_GRANTED
+                            ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+                }
+            } else {
+                return (mContext.checkPermission(permission, attributionSource.pid,
+                            attributionSource.uid) == PackageManager.PERMISSION_GRANTED)
+                        ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+            }
+        }
+        try {
+            return mService.checkPermission(permission, attributionSource, message, forDataDelivery,
+                    startDataDelivery, fromDatasource, attributedOp);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
+    }
+
+    /**
+     * Finishes an app op by validating the entire attribution source chain.
+     *
+     * @param op The op to finish.
+     * @param attributionSource The attribution chain to finish.
+     * @param fromDatasource Whether the finish is by a datasource (skip finish for the
+     *     first attribution source in the chain as this is the datasource)
+     */
+    public void finishDataDelivery(int op, @NonNull AttributionSourceState attributionSource,
+            boolean fromDatasource) {
+        Objects.requireNonNull(attributionSource);
+        try {
+            mService.finishDataDelivery(op, attributionSource, fromDatasource);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks an app op by validating the entire attribution source chain. The op is
+     * also noted/started for the entire attribution chain.
+     *
+     * @param op The op to check.
+     * @param attributionSource The attribution chain to check.
+     * @param message Message associated with the permission if permission has an app op
+     * @param forDataDelivery Whether the check is for delivering data if permission has an app op
+     * @param startDataDelivery Whether to start data delivery (start op) if permission has
+     *     an app op
+     * @return The op check result.
+     */
+    @PermissionResult
+    public int checkOp(int op, @NonNull AttributionSourceState attributionSource,
+            @Nullable String message, boolean forDataDelivery, boolean startDataDelivery) {
+        Objects.requireNonNull(attributionSource);
+        try {
+            return mService.checkOp(op, attributionSource, message, forDataDelivery,
+                    startDataDelivery);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
+    }
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index d490e7a..f3cc35b3 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1156,13 +1156,12 @@
      * @hide
      */
     @TestApi
-    public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
+    public void registerAttributionSource(@NonNull AttributionSource source) {
         try {
-            return mPermissionManager.registerAttributionSource(source);
+            mPermissionManager.registerAttributionSource(source);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 0c0d70c..431bf4c 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -597,13 +597,6 @@
     @TestApi
     public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
 
-    /**
-     * Trace error logger properties definitions.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_TRACE_ERROR_LOGGER = "trace_error_logger";
-
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cb87653..36bc3e7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6661,20 +6661,6 @@
         public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category.";
 
         /**
-         * Whether or not compress blocks should be released on install.
-         * <p>The setting only determines if the platform will attempt to release
-         * compress blocks; it does not guarantee that the files will have their
-         * compress blocks released. Compression is currently only supported on
-         * some f2fs filesystems.
-         * <p>
-         * Type: int (0 for false, 1 for true)
-         *
-         * @hide
-         */
-        public static final String RELEASE_COMPRESS_BLOCKS_ON_INSTALL =
-                "release_compress_blocks_on_install";
-
-        /**
          * List of input methods that are currently enabled.  This is a string
          * containing the IDs of all enabled input methods, each ID separated
          * by ':'.
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 87037f4..47f2c64 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -22,6 +22,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.os.Handler;
 import android.os.Looper;
@@ -31,6 +32,7 @@
 import android.os.SharedMemory;
 import android.util.Slog;
 
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
 
 import java.io.PrintWriter;
@@ -64,7 +66,8 @@
         mAudioFormat = audioFormat;
         mCallback = callback;
         mHandler = new Handler(Looper.getMainLooper());
-        updateStateLocked(options, sharedMemory, null /* callback */);
+        updateStateLocked(options, sharedMemory,
+                new InitializationStateListener(mHandler, mCallback));
     }
 
     @RequiresPermission(RECORD_AUDIO)
@@ -133,6 +136,58 @@
         }
     }
 
+    private static class InitializationStateListener
+            extends IHotwordRecognitionStatusCallback.Stub {
+        private final Handler mHandler;
+        private final HotwordDetector.Callback mCallback;
+
+        InitializationStateListener(Handler handler, HotwordDetector.Callback callback) {
+            this.mHandler = handler;
+            this.mCallback = callback;
+        }
+
+        @Override
+        public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent)
+                throws RemoteException {
+
+        }
+
+        @Override
+        public void onGenericSoundTriggerDetected(
+                SoundTrigger.GenericRecognitionEvent recognitionEvent) throws RemoteException {
+
+        }
+
+        @Override
+        public void onRejected(HotwordRejectedResult result) throws RemoteException {
+
+        }
+
+        @Override
+        public void onError(int status) throws RemoteException {
+
+        }
+
+        @Override
+        public void onRecognitionPaused() throws RemoteException {
+
+        }
+
+        @Override
+        public void onRecognitionResumed() throws RemoteException {
+
+        }
+
+        @Override
+        public void onStatusReported(int status) {
+            Slog.v(TAG, "onStatusReported" + (DEBUG ? "(" + status + ")" : ""));
+            mHandler.sendMessage(obtainMessage(
+                    HotwordDetector.Callback::onHotwordDetectionServiceInitialized,
+                    mCallback,
+                    status));
+        }
+    }
+
     /** @hide */
     public void dump(String prefix, PrintWriter pw) {
         // TODO: implement this
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index bb48757..b9ff5e7 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -65,22 +65,20 @@
     private static final String TAG = "RecognitionService";
 
     /** Debugging flag */
-    private static final boolean DBG = true;
-
-    private static final String RECORD_AUDIO_APP_OP =
-            AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
-    private static final int RECORD_AUDIO_APP_OP_CODE =
-            AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
+    private static final boolean DBG = false;
 
     /** Binder of the recognition service */
     private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
 
     /**
      * The current callback of an application that invoked the
+     *
      * {@link RecognitionService#onStartListening(Intent, Callback)} method
      */
     private Callback mCurrentCallback = null;
 
+    private boolean mStartedDataDelivery;
+
     private static final int MSG_START_LISTENING = 1;
 
     private static final int MSG_STOP_LISTENING = 2;
@@ -120,6 +118,11 @@
                 mCurrentCallback = new Callback(listener, attributionSource);
 
                 RecognitionService.this.onStartListening(intent, mCurrentCallback);
+                if (!checkPermissionAndStartDataDelivery()) {
+                    listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+                    Log.i(TAG, "caller doesn't have permission:"
+                            + Manifest.permission.RECORD_AUDIO);
+                }
             } else {
                 listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
                 Log.i(TAG, "concurrent startListening received - ignoring this call");
@@ -152,13 +155,15 @@
             Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
         } else { // the correct state
             RecognitionService.this.onCancel(mCurrentCallback);
-            mCurrentCallback = null;
+            dispatchClearCallback();
             if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
         }
     }
 
     private void dispatchClearCallback() {
+        finishDataDelivery();
         mCurrentCallback = null;
+        mStartedDataDelivery = false;
     }
 
     private class StartListeningArgs {
@@ -177,7 +182,30 @@
 
     /**
      * Notifies the service that it should start listening for speech.
-     * 
+     *
+     * <p> If you are recognizing speech from the microphone, in this callback you
+     * should create an attribution context for the caller such that when you access
+     * the mic the caller would be properly blamed (and their permission checked in
+     * the process) for accessing the microphone and that you served as a proxy for
+     * this sensitive data (and your permissions would be checked in the process).
+     * You should also open the mic in this callback via the attribution context
+     * and close the mic before returning the recognized result. If you don't do
+     * that then the caller would be blamed and you as being a proxy as well as you
+     * would get one more blame on yourself when you open the microphone.
+     *
+     * <pre>
+     * Context attributionContext = context.createContext(new ContextParams.Builder()
+     *     .setNextAttributionSource(callback.getCallingAttributionSource())
+     *     .build());
+     *
+     * AudioRecord recorder = AudioRecord.Builder()
+     *     .setContext(attributionContext);
+     *     . . .
+     *    .build();
+     *
+     * recorder.startRecording()
+     * </pre>
+     *
      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
      *        may also contain optional extras, see {@link RecognizerIntent}. If these values are
      *        not set explicitly, default values should be used by the recognizer.
@@ -335,57 +363,13 @@
             return mCallingAttributionSource;
         }
 
-        boolean maybeStartAttribution() {
-            if (DBG) {
-                Log.i(TAG, "Starting attribution");
-            }
-
-            if (DBG && isProxyingRecordAudioToCaller()) {
-                Log.i(TAG, "Proxying already in progress, not starting the attribution");
-            }
-
-            if (!isProxyingRecordAudioToCaller()) {
+        @NonNull Context getAttributionContextForCaller() {
+            if (mAttributionContext == null) {
                 mAttributionContext = createContext(new ContextParams.Builder()
                         .setNextAttributionSource(mCallingAttributionSource)
                         .build());
-
-                final int result = PermissionChecker.checkPermissionAndStartDataDelivery(
-                        RecognitionService.this,
-                        Manifest.permission.RECORD_AUDIO,
-                        mAttributionContext.getAttributionSource(),
-                        /*message*/ null);
-
-                return result == PermissionChecker.PERMISSION_GRANTED;
             }
-            return false;
-        }
-
-        void maybeFinishAttribution() {
-            if (DBG) {
-                Log.i(TAG, "Finishing attribution");
-            }
-
-            if (DBG && !isProxyingRecordAudioToCaller()) {
-                Log.i(TAG, "Not proxying currently, not finishing the attribution");
-            }
-
-            if (isProxyingRecordAudioToCaller()) {
-                PermissionChecker.finishDataDelivery(
-                        RecognitionService.this,
-                        RECORD_AUDIO_APP_OP,
-                        mAttributionContext.getAttributionSource());
-
-                mAttributionContext = null;
-            }
-        }
-
-        private boolean isProxyingRecordAudioToCaller() {
-            final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
-            return appOpsManager.isProxying(
-                    RECORD_AUDIO_APP_OP_CODE,
-                    getAttributionTag(),
-                    mCallingAttributionSource.getUid(),
-                    mCallingAttributionSource.getPackageName());
+            return mAttributionContext;
         }
     }
 
@@ -435,4 +419,35 @@
             mServiceRef.clear();
         }
     }
+
+    private boolean checkPermissionAndStartDataDelivery() {
+        if (isPerformingDataDelivery()) {
+            return true;
+        }
+        if (PermissionChecker.checkPermissionAndStartDataDelivery(
+                RecognitionService.this, Manifest.permission.RECORD_AUDIO,
+                mCurrentCallback.getAttributionContextForCaller().getAttributionSource(),
+                /*message*/ null) == PermissionChecker.PERMISSION_GRANTED) {
+            mStartedDataDelivery = true;
+        }
+        return mStartedDataDelivery;
+    }
+
+    void finishDataDelivery() {
+        if (mStartedDataDelivery) {
+            mStartedDataDelivery = false;
+            final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
+            PermissionChecker.finishDataDelivery(RecognitionService.this, op,
+                    mCurrentCallback.getAttributionContextForCaller().getAttributionSource());
+        }
+    }
+
+    @SuppressWarnings("ConstantCondition")
+    private boolean isPerformingDataDelivery() {
+        final int op = AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
+        final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+        return appOpsManager.isProxying(op, getAttributionTag(),
+                mCurrentCallback.getCallingAttributionSource().getUid(),
+                mCurrentCallback.getCallingAttributionSource().getPackageName());
+    }
 }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c9a7949..6c3c383 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -68,7 +68,6 @@
         DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true");
 
         DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
-        DEFAULT_FLAGS.put("settings_silky_home", "true");
         DEFAULT_FLAGS.put("settings_contextual_home", "false");
         DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "true");
         DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index ce649cc..7bad5cb 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -267,20 +267,6 @@
     oneway void updatePointerIcon(IWindow window);
 
     /**
-     * Reparent the top layers for a display to the requested SurfaceControl. The display that is
-     * going to be re-parented (the displayId passed in) needs to have been created by the same
-     * process that is requesting the re-parent. This is to ensure clients can't just re-parent
-     * display content info to any SurfaceControl, as this would be a security issue.
-     *
-     * @param window The window which owns the SurfaceControl. This indicates the z-order of the
-     *               windows of this display against the windows on the parent display.
-     * @param sc The SurfaceControl that the top level layers for the display should be re-parented
-     *           to.
-     * @param displayId The id of the display to be re-parented.
-     */
-    oneway void reparentDisplayContent(IWindow window, in SurfaceControl sc, int displayId);
-
-    /**
      * Update the location of a child display in its parent window. This enables windows in the
      * child display to compute the global transformation matrix.
      *
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 45c4935..f34cd8f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -34,7 +34,6 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
-import android.graphics.BLASTBufferQueue;
 import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
@@ -647,6 +646,12 @@
     public static final int METADATA_OWNER_PID = 6;
 
     /**
+     * game mode for the layer - used for metrics
+     * @hide
+     */
+    public static final int METADATA_GAME_MODE = 8;
+
+    /**
      * A wrapper around HardwareBuffer that contains extra information about how to
      * interpret the screenshot HardwareBuffer.
      *
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 72d403e..ae54f51 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -444,11 +444,6 @@
     }
 
     @Override
-    public void reparentDisplayContent(android.view.IWindow window, android.view.SurfaceControl sc,
-            int displayId) {
-    }
-
-    @Override
     public void updateDisplayContentLocation(android.view.IWindow window, int x, int y,
             int displayId) {
     }
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 207385d..f2827f3 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -76,6 +77,11 @@
     public static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_ATOP;
 
     /**
+     * Completely disable edge effect
+     */
+    private static final int TYPE_NONE = -1;
+
+    /**
      * Use a color edge glow for the edge effect.
      */
     private static final int TYPE_GLOW = 0;
@@ -114,7 +120,7 @@
     private static final float ON_ABSORB_VELOCITY_ADJUSTMENT = 13f;
 
     /** @hide */
-    @IntDef({TYPE_GLOW, TYPE_STRETCH})
+    @IntDef({TYPE_NONE, TYPE_GLOW, TYPE_STRETCH})
     @Retention(RetentionPolicy.SOURCE)
     public @interface EdgeEffectType {
     }
@@ -195,6 +201,12 @@
     private float mBaseGlowScale;
     private float mDisplacement = 0.5f;
     private float mTargetDisplacement = 0.5f;
+
+    /**
+     * Current edge effect type, consumers should always query
+     * {@link #getCurrentEdgeEffectBehavior()} instead of this parameter
+     * directly in case animations have been disabled (ex. for accessibility reasons)
+     */
     private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW;
     private Matrix mTmpMatrix = null;
     private float[] mTmpPoints = null;
@@ -227,6 +239,15 @@
         mPaint.setBlendMode(DEFAULT_BLEND_MODE);
     }
 
+    @EdgeEffectType
+    private int getCurrentEdgeEffectBehavior() {
+        if (!ValueAnimator.areAnimatorsEnabled()) {
+            return TYPE_NONE;
+        } else {
+            return mEdgeEffectType;
+        }
+    }
+
     /**
      * Set the size of this edge effect in pixels.
      *
@@ -302,14 +323,19 @@
      *                     Values may be from 0-1.
      */
     public void onPull(float deltaDistance, float displacement) {
+        int edgeEffectBehavior = getCurrentEdgeEffectBehavior();
+        if (edgeEffectBehavior == TYPE_NONE) {
+            finish();
+            return;
+        }
         final long now = AnimationUtils.currentAnimationTimeMillis();
         mTargetDisplacement = displacement;
         if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration
-                && mEdgeEffectType == TYPE_GLOW) {
+                && edgeEffectBehavior == TYPE_GLOW) {
             return;
         }
         if (mState != STATE_PULL) {
-            if (mEdgeEffectType == TYPE_STRETCH) {
+            if (edgeEffectBehavior == TYPE_STRETCH) {
                 // Restore the mPullDistance to the fraction it is currently showing -- we want
                 // to "catch" the current stretch value.
                 mPullDistance = mDistance;
@@ -342,7 +368,7 @@
 
         mGlowAlphaFinish = mGlowAlpha;
         mGlowScaleYFinish = mGlowScaleY;
-        if (mEdgeEffectType == TYPE_STRETCH && mDistance == 0) {
+        if (edgeEffectBehavior == TYPE_STRETCH && mDistance == 0) {
             mState = STATE_IDLE;
         }
     }
@@ -377,13 +403,17 @@
      * 0 and <code>deltaDistance</code>.
      */
     public float onPullDistance(float deltaDistance, float displacement) {
+        int edgeEffectBehavior = getCurrentEdgeEffectBehavior();
+        if (edgeEffectBehavior == TYPE_NONE) {
+            return 0f;
+        }
         float finalDistance = Math.max(0f, deltaDistance + mDistance);
         float delta = finalDistance - mDistance;
         if (delta == 0f && mDistance == 0f) {
             return 0f; // No pull, don't do anything.
         }
 
-        if (mState != STATE_PULL && mState != STATE_PULL_DECAY && mEdgeEffectType == TYPE_GLOW) {
+        if (mState != STATE_PULL && mState != STATE_PULL_DECAY && edgeEffectBehavior == TYPE_GLOW) {
             // Catch the edge glow in the middle of an animation.
             mPullDistance = mDistance;
             mState = STATE_PULL;
@@ -442,11 +472,12 @@
      * @param velocity Velocity at impact in pixels per second.
      */
     public void onAbsorb(int velocity) {
-        if (mEdgeEffectType == TYPE_STRETCH) {
+        int edgeEffectBehavior = getCurrentEdgeEffectBehavior();
+        if (edgeEffectBehavior == TYPE_STRETCH) {
             mState = STATE_RECEDE;
             mVelocity = velocity * ON_ABSORB_VELOCITY_ADJUSTMENT;
             mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        } else {
+        } else if (edgeEffectBehavior == TYPE_GLOW) {
             mState = STATE_ABSORB;
             mVelocity = 0;
             velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY);
@@ -470,6 +501,8 @@
                     mGlowAlphaStart,
                     Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
             mTargetDisplacement = 0.5f;
+        } else {
+            finish();
         }
     }
 
@@ -532,7 +565,8 @@
      *         animation
      */
     public boolean draw(Canvas canvas) {
-        if (mEdgeEffectType == TYPE_GLOW) {
+        int edgeEffectBehavior = getCurrentEdgeEffectBehavior();
+        if (edgeEffectBehavior == TYPE_GLOW) {
             update();
             final int count = canvas.save();
 
@@ -549,7 +583,7 @@
             mPaint.setAlpha((int) (0xff * mGlowAlpha));
             canvas.drawCircle(centerX, centerY, mRadius, mPaint);
             canvas.restoreToCount(count);
-        } else if (canvas instanceof RecordingCanvas) {
+        } else if (edgeEffectBehavior == TYPE_STRETCH && canvas instanceof RecordingCanvas) {
             if (mState == STATE_RECEDE) {
                 updateSpring();
             }
@@ -604,8 +638,8 @@
                 );
             }
         } else {
-            // This is TYPE_STRETCH and drawing into a Canvas that isn't a Recording Canvas,
-            // so no effect can be shown. Just end the effect.
+            // Animations have been disabled or this is TYPE_STRETCH and drawing into a Canvas
+            // that isn't a Recording Canvas, so no effect can be shown. Just end the effect.
             mState = STATE_IDLE;
             mDistance = 0;
             mVelocity = 0;
diff --git a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
index 510af77..ae6ad32 100644
--- a/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsActiveCallback.aidl
@@ -18,5 +18,6 @@
 
 // Iterface to observe op active changes
 oneway interface IAppOpsActiveCallback {
-    void opActiveChanged(int op, int uid, String packageName, boolean active);
+    void opActiveChanged(int op, int uid, String packageName, String attributionTag,
+            boolean active, int attributionFlags, int attributionChainId);
 }
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index c112d09..9ad4572 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -41,7 +41,8 @@
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
     SyncNotedAppOp startOperation(IBinder clientId, int code, int uid, String packageName,
             @nullable String attributionTag, boolean startIfModeDefault,
-            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
+            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+            int attributionFlags, int attributionChainId);
     @UnsupportedAppUsage
     void finishOperation(IBinder clientId, int code, int uid, String packageName,
             @nullable String attributionTag);
@@ -57,10 +58,12 @@
     SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             boolean skipProxyOperation);
-    SyncNotedAppOp startProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource,
+    SyncNotedAppOp startProxyOperation(int code, in AttributionSource attributionSource,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-            boolean shouldCollectMessage, boolean skipProxyOperation);
-    void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource);
+            boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags,
+            int proxiedAttributionFlags, int attributionChainId);
+    void finishProxyOperation(int code, in AttributionSource attributionSource,
+            boolean skipProxyOperation);
 
     // Remaining methods are only used in Java.
     int checkPackage(int uid, String packageName);
diff --git a/core/java/com/android/internal/content/F2fsUtils.java b/core/java/com/android/internal/content/F2fsUtils.java
deleted file mode 100644
index 27f1b30..0000000
--- a/core/java/com/android/internal/content/F2fsUtils.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.content;
-
-import android.annotation.NonNull;
-import android.content.ContentResolver;
-import android.os.Environment;
-import android.os.incremental.IncrementalManager;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Utility methods to work with the f2fs file system.
- */
-public final class F2fsUtils {
-    private static final String TAG = "F2fsUtils";
-    private static final boolean DEBUG_F2FS = false;
-
-    /** Directory containing kernel features */
-    private static final File sKernelFeatures =
-            new File("/sys/fs/f2fs/features");
-    /** File containing features enabled on "/data" */
-    private static final File sUserDataFeatures =
-            new File("/dev/sys/fs/by-name/userdata/features");
-    private static final File sDataDirectory = Environment.getDataDirectory();
-    /** Name of the compression feature */
-    private static final String COMPRESSION_FEATURE = "compression";
-
-    private static final boolean sKernelCompressionAvailable;
-    private static final boolean sUserDataCompressionAvailable;
-
-    static {
-        sKernelCompressionAvailable = isCompressionEnabledInKernel();
-        if (!sKernelCompressionAvailable) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "f2fs compression DISABLED; feature not part of the kernel");
-            }
-        }
-        sUserDataCompressionAvailable = isCompressionEnabledOnUserData();
-        if (!sUserDataCompressionAvailable) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "f2fs compression DISABLED; feature not enabled on filesystem");
-            }
-        }
-    }
-
-    /**
-     * Releases compressed blocks from eligible installation artifacts.
-     * <p>
-     * Modern f2fs implementations starting in {@code S} support compression
-     * natively within the file system. The data blocks of specific installation
-     * artifacts [eg. .apk, .so, ...] can be compressed at the file system level,
-     * making them look and act like any other uncompressed file, but consuming
-     * a fraction of the space.
-     * <p>
-     * However, the unused space is not free'd automatically. Instead, we must
-     * manually tell the file system to release the extra blocks [the delta between
-     * the compressed and uncompressed block counts] back to the free pool.
-     * <p>
-     * Because of how compression works within the file system, once the blocks
-     * have been released, the file becomes read-only and cannot be modified until
-     * the free'd blocks have again been reserved from the free pool.
-     */
-    public static void releaseCompressedBlocks(ContentResolver resolver, File file) {
-        if (!sKernelCompressionAvailable || !sUserDataCompressionAvailable) {
-            return;
-        }
-
-        // NOTE: Retrieving this setting means we need to delay releasing cblocks
-        // of any APKs installed during the PackageManagerService constructor. Instead
-        // of being able to release them in the constructor, they can only be released
-        // immediately prior to the system being available. When we no longer need to
-        // read this setting, move cblock release back to the package manager constructor.
-        final boolean releaseCompressBlocks =
-                Secure.getInt(resolver, Secure.RELEASE_COMPRESS_BLOCKS_ON_INSTALL, 1) != 0;
-        if (!releaseCompressBlocks) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "SKIP; release compress blocks not enabled");
-            }
-            return;
-        }
-        if (!isCompressionAllowed(file)) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "SKIP; compression not allowed");
-            }
-            return;
-        }
-        final File[] files = getFilesToRelease(file);
-        if (files == null || files.length == 0) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "SKIP; no files to compress");
-            }
-            return;
-        }
-        for (int i = files.length - 1; i >= 0; --i) {
-            final long releasedBlocks = nativeReleaseCompressedBlocks(files[i].getAbsolutePath());
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "RELEASED " + releasedBlocks + " blocks"
-                        + " from \"" + files[i] + "\"");
-            }
-        }
-    }
-
-    /**
-     * Returns {@code true} if compression is allowed on the file system containing
-     * the given file.
-     * <p>
-     * NOTE: The return value does not mean if the given file, or any other file
-     * on the same file system, is actually compressed. It merely determines whether
-     * not files <em>may</em> be compressed.
-     */
-    private static boolean isCompressionAllowed(@NonNull File file) {
-        final String filePath;
-        try {
-            filePath = file.getCanonicalPath();
-        } catch (IOException e) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "f2fs compression DISABLED; could not determine path");
-            }
-            return false;
-        }
-        if (IncrementalManager.isIncrementalPath(filePath)) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "f2fs compression DISABLED; file on incremental fs");
-            }
-            return false;
-        }
-        if (!isChild(sDataDirectory, filePath)) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "f2fs compression DISABLED; file not on /data");
-            }
-            return false;
-        }
-        if (DEBUG_F2FS) {
-            Slog.d(TAG, "f2fs compression ENABLED");
-        }
-        return true;
-    }
-
-    /**
-     * Returns {@code true} if the given child is a descendant of the base.
-     */
-    private static boolean isChild(@NonNull File base, @NonNull String childPath) {
-        try {
-            base = base.getCanonicalFile();
-
-            File parentFile = new File(childPath).getCanonicalFile();
-            while (parentFile != null) {
-                if (base.equals(parentFile)) {
-                    return true;
-                }
-                parentFile = parentFile.getParentFile();
-            }
-            return false;
-        } catch (IOException ignore) {
-            return false;
-        }
-    }
-
-    /**
-     * Returns whether or not the compression feature is enabled in the kernel.
-     * <p>
-     * NOTE: This doesn't mean compression is enabled on a particular file system
-     * or any files have been compressed. Only that the functionality is enabled
-     * on the device.
-     */
-    private static boolean isCompressionEnabledInKernel() {
-        final File[] features = sKernelFeatures.listFiles();
-        if (features == null || features.length == 0) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "ERROR; no kernel features");
-            }
-            return false;
-        }
-        for (int i = features.length - 1; i >= 0; --i) {
-            final File feature = features[i];
-            if (COMPRESSION_FEATURE.equals(features[i].getName())) {
-                if (DEBUG_F2FS) {
-                    Slog.d(TAG, "FOUND kernel compression feature");
-                }
-                return true;
-            }
-        }
-        if (DEBUG_F2FS) {
-            Slog.d(TAG, "ERROR; kernel compression feature not found");
-        }
-        return false;
-    }
-
-    /**
-     * Returns whether or not the compression feature is enabled on user data [ie. "/data"].
-     * <p>
-     * NOTE: This doesn't mean any files have been compressed. Only that the functionality
-     * is enabled on the file system.
-     */
-    private static boolean isCompressionEnabledOnUserData() {
-        if (!sUserDataFeatures.exists()
-                || !sUserDataFeatures.isFile()
-                || !sUserDataFeatures.canRead()) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "ERROR; filesystem features not available");
-            }
-            return false;
-        }
-        final List<String> configLines;
-        try {
-            configLines = Files.readAllLines(sUserDataFeatures.toPath());
-        } catch (IOException ignore) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "ERROR; couldn't read filesystem features");
-            }
-            return false;
-        }
-        if (configLines == null
-                || configLines.size() > 1
-                || TextUtils.isEmpty(configLines.get(0))) {
-            if (DEBUG_F2FS) {
-                Slog.d(TAG, "ERROR; no filesystem features");
-            }
-            return false;
-        }
-        final String[] features = configLines.get(0).split(",");
-        for (int i = features.length - 1; i >= 0; --i) {
-            if (COMPRESSION_FEATURE.equals(features[i].trim())) {
-                if (DEBUG_F2FS) {
-                    Slog.d(TAG, "FOUND filesystem compression feature");
-                }
-                return true;
-            }
-        }
-        if (DEBUG_F2FS) {
-            Slog.d(TAG, "ERROR; filesystem compression feature not found");
-        }
-        return false;
-    }
-
-    /**
-     * Returns all files contained within the directory at any depth from the given path.
-     */
-    private static List<File> getFilesRecursive(@NonNull File path) {
-        final File[] allFiles = path.listFiles();
-        if (allFiles == null) {
-            return null;
-        }
-        final ArrayList<File> files = new ArrayList<>();
-        for (File f : allFiles) {
-            if (f.isDirectory()) {
-                files.addAll(getFilesRecursive(f));
-            } else if (f.isFile()) {
-                files.add(f);
-            }
-        }
-        return files;
-    }
-
-    /**
-     * Returns all files contained within the directory at any depth from the given path.
-     */
-    private static File[] getFilesToRelease(@NonNull File codePath) {
-        final List<File> files = getFilesRecursive(codePath);
-        if (files == null) {
-            if (codePath.isFile()) {
-                return new File[] { codePath };
-            }
-            return null;
-        }
-        if (files.size() == 0) {
-            return null;
-        }
-        return files.toArray(new File[files.size()]);
-    }
-
-    private static native long nativeReleaseCompressedBlocks(String path);
-
-}
diff --git a/core/java/com/android/internal/content/OWNERS b/core/java/com/android/internal/content/OWNERS
deleted file mode 100644
index c42bee6..0000000
--- a/core/java/com/android/internal/content/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 36137
-include /core/java/android/content/pm/OWNERS
-
-per-file ReferrerIntent.aidl = file:/services/core/java/com/android/server/am/OWNERS
-per-file ReferrerIntent.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 4502a33..9986834 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -84,7 +84,6 @@
         android: {
             srcs: [
                 "AndroidRuntime.cpp",
-                "com_android_internal_content_F2fsUtils.cpp",
                 "com_android_internal_content_NativeLibraryHelper.cpp",
                 "com_google_android_gles_jni_EGLImpl.cpp",
                 "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
@@ -223,7 +222,6 @@
                 "fd_utils.cpp",
                 "android_hardware_input_InputWindowHandle.cpp",
                 "android_hardware_input_InputApplicationHandle.cpp",
-                "permission_utils.cpp",
             ],
 
             static_libs: [
@@ -242,7 +240,6 @@
                 "audioflinger-aidl-cpp",
                 "av-types-aidl-cpp",
                 "android.hardware.camera.device@3.2",
-                "media_permission-aidl-cpp",
                 "libandroidicu",
                 "libbpf_android",
                 "libnetdbpf",
@@ -259,6 +256,7 @@
                 "libgraphicsenv",
                 "libgui",
                 "libmediandk",
+                "libpermission",
                 "libsensor",
                 "libinput",
                 "libcamera_client",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index dca5d96..8f26d35 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -98,7 +98,6 @@
 extern int register_android_media_MicrophoneInfo(JNIEnv *env);
 extern int register_android_media_ToneGenerator(JNIEnv *env);
 extern int register_android_media_midi(JNIEnv *env);
-extern int register_android_media_permission_Identity(JNIEnv* env);
 
 namespace android {
 
@@ -190,7 +189,6 @@
 extern int register_android_content_res_Configuration(JNIEnv* env);
 extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
 extern int register_android_security_Scrypt(JNIEnv *env);
-extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
@@ -1592,7 +1590,6 @@
         REG_JNI(register_android_media_RemoteDisplay),
         REG_JNI(register_android_media_ToneGenerator),
         REG_JNI(register_android_media_midi),
-        REG_JNI(register_android_media_permission_Identity),
 
         REG_JNI(register_android_opengl_classes),
         REG_JNI(register_android_server_NetworkManagementSocketTagger),
@@ -1622,7 +1619,6 @@
 
         REG_JNI(register_android_animation_PropertyValuesHolder),
         REG_JNI(register_android_security_Scrypt),
-        REG_JNI(register_com_android_internal_content_F2fsUtils),
         REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
         REG_JNI(register_com_android_internal_os_DmabufInfoReader),
         REG_JNI(register_com_android_internal_os_FuseAppLoop),
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 83dc1e0..bce4ed7 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -22,13 +22,15 @@
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
-#include "permission_utils.h"
 
 #include <utils/Log.h>
 #include <media/AudioRecord.h>
 #include <media/MicrophoneInfo.h>
 #include <vector>
 
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
+
 #include <nativehelper/ScopedUtfChars.h>
 
 #include "android_media_AudioFormat.h"
@@ -38,10 +40,8 @@
 #include "android_media_MicrophoneInfo.h"
 #include "android_media_AudioAttributes.h"
 
-// ----------------------------------------------------------------------------
 
-using android::media::permission::convertIdentity;
-using android::media::permission::Identity;
+// ----------------------------------------------------------------------------
 
 using namespace android;
 
@@ -189,7 +189,7 @@
                                             jobject jaa, jintArray jSampleRate, jint channelMask,
                                             jint channelIndexMask, jint audioFormat,
                                             jint buffSizeInBytes, jintArray jSession,
-                                            jobject jIdentity, jlong nativeRecordInJavaObj,
+                                            jobject jAttributionSource, jlong nativeRecordInJavaObj,
                                             jint sharedAudioHistoryMs) {
     //ALOGV(">> Entering android_media_AudioRecord_setup");
     //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
@@ -260,14 +260,18 @@
         size_t bytesPerSample = audio_bytes_per_sample(format);
 
         if (buffSizeInBytes == 0) {
-             ALOGE("Error creating AudioRecord: frameCount is 0.");
+            ALOGE("Error creating AudioRecord: frameCount is 0.");
             return (jint) AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT;
         }
         size_t frameSize = channelCount * bytesPerSample;
         size_t frameCount = buffSizeInBytes / frameSize;
 
         // create an uninitialized AudioRecord object
-        lpRecorder = new AudioRecord(convertIdentity(env, jIdentity));
+        Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
+        android::content::AttributionSourceState attributionSource;
+        attributionSource.readFromParcel(parcel);
+
+        lpRecorder = new AudioRecord(attributionSource);
 
         // read the AudioAttributes values
         auto paa = JNIAudioAttributeHelper::makeUnique();
@@ -912,7 +916,7 @@
         {"native_start", "(II)I", (void *)android_media_AudioRecord_start},
         {"native_stop", "()V", (void *)android_media_AudioRecord_stop},
         {"native_setup",
-         "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;JI)I",
+         "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JI)I",
          (void *)android_media_AudioRecord_setup},
         {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
         {"native_release", "()V", (void *)android_media_AudioRecord_release},
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index de5df20..73d2d8d 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -48,7 +48,6 @@
 using namespace android;
 
 using ::android::media::VolumeShaper;
-using ::android::media::permission::Identity;
 
 // ----------------------------------------------------------------------------
 static const char* const kClassPathName = "android/media/AudioTrack";
@@ -330,9 +329,10 @@
         // create the native AudioTrack object
         ScopedUtfChars opPackageNameStr(env, opPackageName);
         // TODO b/182469354: make consistent with AudioRecord
-        Identity identity = Identity();
-        identity.packageName = std::string(opPackageNameStr.c_str());
-        lpTrack = new AudioTrack(identity);
+        AttributionSourceState attributionSource;
+        attributionSource.packageName = std::string(opPackageNameStr.c_str());
+        attributionSource.token = sp<BBinder>::make();
+        lpTrack = new AudioTrack(attributionSource);
 
         // read the AudioAttributes values
         auto paa = JNIAudioAttributeHelper::makeUnique();
@@ -395,7 +395,7 @@
                                   offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK
                                           : AudioTrack::TRANSFER_SYNC,
                                   (offload || encapsulationMode) ? &offloadInfo : NULL,
-                                  Identity(), // default uid, pid values
+                                  AttributionSourceState(), // default uid, pid values
                                   paa.get());
             break;
 
@@ -421,7 +421,7 @@
                                   sessionId,              // audio session ID
                                   AudioTrack::TRANSFER_SHARED,
                                   NULL,       // default offloadInfo
-                                  Identity(), // default uid, pid values
+                                  AttributionSourceState(), // default uid, pid values
                                   paa.get());
             break;
 
diff --git a/core/jni/com_android_internal_content_F2fsUtils.cpp b/core/jni/com_android_internal_content_F2fsUtils.cpp
deleted file mode 100644
index 8b9d59c..0000000
--- a/core/jni/com_android_internal_content_F2fsUtils.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "F2fsUtils"
-
-#include "core_jni_helpers.h"
-
-#include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/jni_macros.h>
-
-#include <sys/ioctl.h>
-#include <sys/types.h>
-
-#include <linux/f2fs.h>
-#include <linux/fs.h>
-
-#include <android-base/unique_fd.h>
-
-#include <utils/Log.h>
-
-#include <errno.h>
-#include <fcntl.h>
-
-#include <array>
-
-using namespace std::literals;
-
-namespace android {
-
-static jlong com_android_internal_content_F2fsUtils_nativeReleaseCompressedBlocks(JNIEnv *env,
-                                                                                  jclass clazz,
-                                                                                  jstring path) {
-    unsigned long long blkcnt;
-    int ret;
-    ScopedUtfChars filePath(env, path);
-
-    android::base::unique_fd fd(open(filePath.c_str(), O_RDONLY | O_CLOEXEC, 0));
-    if (fd < 0) {
-        ALOGW("Failed to open file: %s (%d)\n", filePath.c_str(), errno);
-        return 0;
-    }
-
-    long flags = 0;
-    ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
-    if (ret < 0) {
-        ALOGW("Failed to get flags for file: %s (%d)\n", filePath.c_str(), errno);
-        return 0;
-    }
-    if ((flags & FS_COMPR_FL) == 0) {
-        return 0;
-    }
-
-    ret = ioctl(fd, F2FS_IOC_RELEASE_COMPRESS_BLOCKS, &blkcnt);
-    if (ret < 0) {
-        return -errno;
-    }
-    return blkcnt;
-}
-
-static const std::array gMethods = {
-        MAKE_JNI_NATIVE_METHOD(
-                "nativeReleaseCompressedBlocks", "(Ljava/lang/String;)J",
-                com_android_internal_content_F2fsUtils_nativeReleaseCompressedBlocks),
-};
-
-int register_com_android_internal_content_F2fsUtils(JNIEnv *env) {
-    return RegisterMethodsOrDie(env, "com/android/internal/content/F2fsUtils", gMethods.data(),
-                                gMethods.size());
-}
-
-}; // namespace android
diff --git a/core/jni/permission_utils.cpp b/core/jni/permission_utils.cpp
deleted file mode 100644
index 2b7ef99..0000000
--- a/core/jni/permission_utils.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "permission_utils.h"
-#include "core_jni_helpers.h"
-
-static struct {
-    jfieldID fieldUid;            // Identity.uid
-    jfieldID fieldPid;            // Identity.pid
-    jfieldID fieldPackageName;    // Identity.packageName
-    jfieldID fieldAttributionTag; // Identity.attributionTag
-} javaIdentityFields;
-
-static const JNINativeMethod method_table[] = {
-        // no static methods, currently
-};
-
-int register_android_media_permission_Identity(JNIEnv* env) {
-    jclass identityClass = android::FindClassOrDie(env, "android/media/permission/Identity");
-    javaIdentityFields.fieldUid = android::GetFieldIDOrDie(env, identityClass, "uid", "I");
-    javaIdentityFields.fieldPid = android::GetFieldIDOrDie(env, identityClass, "pid", "I");
-    javaIdentityFields.fieldPackageName =
-            android::GetFieldIDOrDie(env, identityClass, "packageName", "Ljava/lang/String;");
-    javaIdentityFields.fieldAttributionTag =
-            android::GetFieldIDOrDie(env, identityClass, "attributionTag", "Ljava/lang/String;");
-
-    return android::RegisterMethodsOrDie(env, "android/media/permission/Identity", method_table,
-                                         NELEM(method_table));
-}
-
-namespace android::media::permission {
-
-Identity convertIdentity(JNIEnv* env, const jobject& jIdentity) {
-    Identity identity;
-
-    identity.uid = env->GetIntField(jIdentity, javaIdentityFields.fieldUid);
-    identity.pid = env->GetIntField(jIdentity, javaIdentityFields.fieldPid);
-
-    jstring packageNameStr = static_cast<jstring>(
-            env->GetObjectField(jIdentity, javaIdentityFields.fieldPackageName));
-    if (packageNameStr == nullptr) {
-        identity.packageName = std::nullopt;
-    } else {
-        identity.packageName = std::string(ScopedUtfChars(env, packageNameStr).c_str());
-    }
-
-    jstring attributionTagStr = static_cast<jstring>(
-            env->GetObjectField(jIdentity, javaIdentityFields.fieldAttributionTag));
-    if (attributionTagStr == nullptr) {
-        identity.attributionTag = std::nullopt;
-    } else {
-        identity.attributionTag = std::string(ScopedUtfChars(env, attributionTagStr).c_str());
-    }
-
-    return identity;
-}
-
-} // namespace android::media::permission
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f457c56..6b6cbea 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1625,16 +1625,16 @@
     <eat-comment />
 
     <!-- Allows an application to modify and remove existing voicemails in the system.
-        <p>Protection level: signature|privileged
+        <p>Protection level: signature|privileged|role
     -->
     <permission android:name="com.android.voicemail.permission.WRITE_VOICEMAIL"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- Allows an application to read voicemails in the system.
-         <p>Protection level: signature|privileged
+         <p>Protection level: signature|privileged|role
     -->
     <permission android:name="com.android.voicemail.permission.READ_VOICEMAIL"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- ======================================= -->
     <!-- Permissions for accessing location info -->
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 7262ce5..a64129e 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -281,6 +281,9 @@
     <allow-in-power-save package="com.android.cellbroadcastreceiver" />
     <allow-in-power-save package="com.android.shell" />
 
+    <!-- Emergency app needs to run in the background to reliably provide safety features -->
+    <allow-in-power-save package="com.android.emergency" />
+
     <!-- Whitelist system providers -->
     <!-- Calendar provider needs alarms while in idle -->
     <allow-in-power-save package="com.android.providers.calendar" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 94d13ea..a88be31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -399,6 +399,19 @@
         }
     }
 
+    /** Helper to set int metadata on the Surface corresponding to the task id. */
+    public void setSurfaceMetadata(int taskId, int key, int value) {
+        synchronized (mLock) {
+            final TaskAppearedInfo info = mTasks.get(taskId);
+            if (info == null || info.getLeash() == null) {
+                return;
+            }
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.setMetadata(info.getLeash(), key, value);
+            t.apply();
+        }
+    }
+
     private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
             TaskListener oldListener, TaskListener newListener) {
         if (oldListener == newListener) return false;
diff --git a/core/jni/permission_utils.h b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
similarity index 67%
rename from core/jni/permission_utils.h
rename to libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
index d625bb6..fbbd09f 100644
--- a/core/jni/permission_utils.h
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-#pragma once
+package com.android.wm.shell.tasksurfacehelper;
 
-#include <android/media/permission/Identity.h>
-#include <jni.h>
+/**
+ * Interface to communicate with a Task's SurfaceControl.
+ */
+public interface TaskSurfaceHelper {
 
-namespace android::media::permission {
-
-Identity convertIdentity(JNIEnv* env, const jobject& jIdentity);
+    /** Sets the METADATA_GAME_MODE for the layer corresponding to the task **/
+    default void setGameModeForTask(int taskId, int gameMode) {}
 }
-
-int register_android_media_permission_Identity(JNIEnv* env);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
new file mode 100644
index 0000000..b459b9f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.tasksurfacehelper;
+
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ShellExecutor;
+
+/**
+ * Intermediary controller that communicates with {@link ShellTaskOrganizer} to send commands
+ * to SurfaceControl.
+ */
+public class TaskSurfaceHelperController {
+
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final ShellExecutor mMainExecutor;
+    private final TaskSurfaceHelperImpl mImpl = new TaskSurfaceHelperImpl();
+
+    public TaskSurfaceHelperController(ShellTaskOrganizer taskOrganizer,
+            ShellExecutor mainExecutor) {
+        mTaskOrganizer = taskOrganizer;
+        mMainExecutor = mainExecutor;
+    }
+
+    public TaskSurfaceHelper asTaskSurfaceHelper() {
+        return mImpl;
+    }
+
+    /**
+     * Sends a Transaction to set the game mode metadata on the
+     * corresponding SurfaceControl
+     */
+    public void setGameModeForTask(int taskId, int gameMode) {
+        mTaskOrganizer.setSurfaceMetadata(taskId, SurfaceControl.METADATA_GAME_MODE, gameMode);
+    }
+
+    private class TaskSurfaceHelperImpl implements TaskSurfaceHelper {
+        @Override
+        public void setGameModeForTask(int taskId, int gameMode) {
+            mMainExecutor.execute(() -> {
+                TaskSurfaceHelperController.this.setGameModeForTask(taskId, gameMode);
+            });
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
new file mode 100644
index 0000000..d614275
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.tasksurfacehelper;
+
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class TaskSurfaceHelperControllerTest {
+    private TaskSurfaceHelperController mTaskSurfaceHelperController;
+    @Mock
+    private ShellTaskOrganizer mMockTaskOrganizer;
+    @Mock
+    private ShellExecutor mMockShellExecutor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTaskSurfaceHelperController = new TaskSurfaceHelperController(
+                mMockTaskOrganizer, mMockShellExecutor);
+    }
+
+    @Test
+    public void testSetGameModeForTask() {
+        mTaskSurfaceHelperController.setGameModeForTask(/*taskId*/1, /*gameMode*/3);
+        verify(mMockTaskOrganizer).setSurfaceMetadata(1, SurfaceControl.METADATA_GAME_MODE, 3);
+    }
+}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 0d44a85..7c6ae28 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -16,8 +16,6 @@
 
 package android.media;
 
-import static android.media.permission.PermissionUtil.myIdentity;
-
 import android.annotation.CallbackExecutor;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -29,12 +27,13 @@
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
 import android.content.Context;
 import android.media.MediaRecorder.Source;
 import android.media.audiopolicy.AudioMix;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.metrics.LogSessionId;
-import android.media.permission.Identity;
 import android.media.projection.MediaProjection;
 import android.os.Binder;
 import android.os.Build;
@@ -42,6 +41,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -381,7 +381,8 @@
      *   {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction
      *   time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
      *   construction.
-     * @param context An optional context to pull an attribution tag from.
+     * @param context An optional context on whose behalf the recoding is performed.
+     *
      * @throws IllegalArgumentException
      */
     private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
@@ -449,10 +450,11 @@
 
         audioBuffSizeCheck(bufferSizeInBytes);
 
-        Identity identity = myIdentity(context);
-        if (identity.packageName == null) {
+        AttributionSource attributionSource = (context != null)
+                ? context.getAttributionSource() : AttributionSource.myAttributionSource();
+        if (attributionSource.getPackageName() == null) {
             // Command line utility
-            identity.packageName = "uid:" + Binder.getCallingUid();
+            attributionSource = attributionSource.withPackageName("uid:" + Binder.getCallingUid());
         }
 
         int[] sampleRate = new int[] {mSampleRate};
@@ -461,14 +463,15 @@
 
         //TODO: update native initialization when information about hardware init failure
         //      due to capture device already open is available.
-        int initResult = native_setup(new WeakReference<AudioRecord>(this),
-                mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,
-                mAudioFormat, mNativeBufferSizeInBytes,
-                session, identity, 0 /*nativeRecordInJavaObj*/,
-                maxSharedAudioHistoryMs);
-        if (initResult != SUCCESS) {
-            loge("Error code "+initResult+" when initializing native AudioRecord object.");
-            return; // with mState == STATE_UNINITIALIZED
+        try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+            int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes,
+                    sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
+                    mNativeBufferSizeInBytes, session, attributionSourceState.getParcel(),
+                    0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs);
+            if (initResult != SUCCESS) {
+                loge("Error code " + initResult + " when initializing native AudioRecord object.");
+                return; // with mState == STATE_UNINITIALIZED
+            }
         }
 
         mSampleRate = sampleRate[0];
@@ -512,23 +515,27 @@
      */
     /* package */ void deferred_connect(long  nativeRecordInJavaObj) {
         if (mState != STATE_INITIALIZED) {
-            int[] session = { 0 };
-            int[] rates = { 0 };
+            int[] session = {0};
+            int[] rates = {0};
             //TODO: update native initialization when information about hardware init failure
             //      due to capture device already open is available.
             // Note that for this native_setup, we are providing an already created/initialized
             // *Native* AudioRecord, so the attributes parameters to native_setup() are ignored.
-            int initResult = native_setup(new WeakReference<AudioRecord>(this),
-                    null /*mAudioAttributes*/,
-                    rates /*mSampleRates*/,
-                    0 /*mChannelMask*/,
-                    0 /*mChannelIndexMask*/,
-                    0 /*mAudioFormat*/,
-                    0 /*mNativeBufferSizeInBytes*/,
-                    session,
-                    myIdentity(null),
-                    nativeRecordInJavaObj,
-                    0);
+            final int initResult;
+            try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource()
+                    .asScopedParcelState()) {
+                initResult = native_setup(new WeakReference<>(this),
+                        null /*mAudioAttributes*/,
+                        rates /*mSampleRates*/,
+                        0 /*mChannelMask*/,
+                        0 /*mChannelIndexMask*/,
+                        0 /*mAudioFormat*/,
+                        0 /*mNativeBufferSizeInBytes*/,
+                        session,
+                        attributionSourceState.getParcel(),
+                        nativeRecordInJavaObj,
+                        0);
+            }
             if (initResult != SUCCESS) {
                 loge("Error code "+initResult+" when initializing native AudioRecord object.");
                 return; // with mState == STATE_UNINITIALIZED
@@ -620,8 +627,8 @@
 
         /**
          * Sets the context the record belongs to. This context will be used to pull information,
-         * such as attribution tags, which will be associated with the AudioRecord. However, the
-         * context itself will not be retained by the AudioRecord.
+         * such as {@link android.content.AttributionSource}, which will be associated with
+         * the AudioRecord. However, the context itself will not be retained by the AudioRecord.
          * @param context a non-null {@link Context} instance
          * @return the same Builder instance.
          */
@@ -2216,7 +2223,7 @@
     //--------------------
 
     /**
-     * @deprecated Use native_setup that takes an Identity object
+     * @deprecated Use native_setup that takes an {@link AttributionSource} object
      * @return
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
@@ -2227,18 +2234,20 @@
             int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
             int buffSizeInBytes, int[] sessionId, String opPackageName,
             long nativeRecordInJavaObj) {
-        Identity identity = myIdentity(null);
-        identity.packageName = opPackageName;
-
-        return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask,
-                audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj, 0);
+        AttributionSource attributionSource = AttributionSource.myAttributionSource()
+                .withPackageName(opPackageName);
+        try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+            return native_setup(audiorecordThis, attributes, sampleRate, channelMask,
+                    channelIndexMask, audioFormat, buffSizeInBytes, sessionId,
+                    attributionSourceState.getParcel(), nativeRecordInJavaObj, 0);
+        }
     }
 
     private native int native_setup(Object audiorecordThis,
             Object /*AudioAttributes*/ attributes,
             int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
-            int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj,
-            int maxSharedAudioHistoryMs);
+            int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
+            long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
 
     // TODO remove: implementation calls directly into implementation of native_release()
     private native void native_finalize();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 2d8babd..4f761ba 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.BIND_IMS_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.media.permission.PermissionUtil.myIdentity;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -28,6 +27,8 @@
 import android.annotation.SystemApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -35,7 +36,6 @@
 import android.graphics.SurfaceTexture;
 import android.media.SubtitleController.Anchor;
 import android.media.SubtitleTrack.RenderingWidget;
-import android.media.permission.Identity;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -56,7 +56,6 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
@@ -685,14 +684,18 @@
         mTimeProvider = new TimeProvider(this);
         mOpenSubtitleSources = new Vector<InputStream>();
 
-        Identity identity = myIdentity(null);
+        AttributionSource attributionSource = AttributionSource.myAttributionSource();
         // set the package name to empty if it was null
-        identity.packageName = TextUtils.emptyIfNull(identity.packageName);
+        if (attributionSource.getPackageName() == null) {
+            attributionSource = attributionSource.withPackageName("");
+        }
 
         /* Native setup requires a weak reference to our object.
          * It's easier to create it here than in C++.
          */
-        native_setup(new WeakReference<MediaPlayer>(this), identity);
+        try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+            native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
+        }
 
         baseRegisterPlayer(sessionId);
     }
@@ -2475,7 +2478,8 @@
     private native final int native_setMetadataFilter(Parcel request);
 
     private static native final void native_init();
-    private native void native_setup(Object mediaplayerThis, @NonNull Identity identity);
+    private native void native_setup(Object mediaplayerThis,
+            @NonNull Parcel attributionSource);
     private native final void native_finalize();
 
     /**
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 499034e..1efd4c6 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,8 +16,6 @@
 
 package android.media;
 
-import static android.media.permission.PermissionUtil.myIdentity;
-
 import android.annotation.CallbackExecutor;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -27,14 +25,16 @@
 import android.annotation.SystemApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
 import android.content.Context;
 import android.hardware.Camera;
 import android.media.metrics.LogSessionId;
-import android.media.permission.Identity;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -163,8 +163,11 @@
         /* Native setup requires a weak reference to our object.
          * It's easier to create it here than in C++.
          */
-        native_setup(new WeakReference<MediaRecorder>(this),
-                ActivityThread.currentPackageName(), myIdentity(context));
+        try (ScopedParcelState attributionSourceState = context.getAttributionSource()
+                .asScopedParcelState()) {
+            native_setup(new WeakReference<>(this), ActivityThread.currentPackageName(),
+                    attributionSourceState.getParcel());
+        }
     }
 
     /**
@@ -1894,14 +1897,15 @@
             publicAlternatives = "{@link MediaRecorder}")
     private void native_setup(Object mediarecorderThis,
             String clientName, String opPackageName) throws IllegalStateException {
-        Identity identity = myIdentity(null);
-        identity.packageName = opPackageName;
-
-        native_setup(mediarecorderThis, clientName, identity);
+        AttributionSource attributionSource = AttributionSource.myAttributionSource()
+                .withPackageName(opPackageName);
+        try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
+            native_setup(mediarecorderThis, clientName, attributionSourceState.getParcel());
+        }
     }
 
     private native void native_setup(Object mediarecorderThis,
-            String clientName, Identity identity)
+            String clientName, @NonNull Parcel attributionSource)
             throws IllegalStateException;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index fd3c405..70bb960 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -16,8 +16,6 @@
 
 package android.media.audiofx;
 
-import static android.media.permission.PermissionUtil.myIdentity;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -26,10 +24,11 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioSystem;
-import android.media.permission.Identity;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -518,10 +517,13 @@
 
         // native initialization
         // TODO b/182469354: Make consistent with AudioRecord
-        int initResult = native_setup(new WeakReference<AudioEffect>(this),
-                type.toString(), uuid.toString(), priority, audioSession,
-                deviceType, deviceAddress,
-                id, desc, myIdentity(null), probe);
+        int initResult;
+        try (ScopedParcelState attributionSourceState =  AttributionSource.myAttributionSource()
+                .asScopedParcelState()) {
+            initResult = native_setup(new WeakReference<>(this), type.toString(), uuid.toString(),
+                    priority, audioSession, deviceType, deviceAddress, id, desc,
+                    attributionSourceState.getParcel(), probe);
+        }
         if (initResult != SUCCESS && initResult != ALREADY_EXISTS) {
             Log.e(TAG, "Error code " + initResult
                     + " when initializing AudioEffect.");
@@ -1388,7 +1390,7 @@
     private native final int native_setup(Object audioeffect_this, String type,
             String uuid, int priority, int audioSession,
             int deviceType, String deviceAddress, int[] id, Object[] desc,
-            Identity identity, boolean probe);
+            @NonNull Parcel attributionSource, boolean probe);
 
     private native final void native_finalize();
 
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index 58c9e65..3349277 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -16,12 +16,13 @@
 
 package android.media.audiofx;
 
-import static android.media.permission.PermissionUtil.myIdentity;
-
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.media.permission.Identity;
+import android.content.AttributionSource;
+import android.content.AttributionSource.ScopedParcelState;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Parcel;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -222,8 +223,12 @@
 
             // native initialization
             // TODO b/182469354: make consistent with AudioRecord
-            int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id,
-                    myIdentity(null));
+            int result;
+            try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource()
+                    .asScopedParcelState()) {
+                result = native_setup(new WeakReference<>(this), audioSession, id,
+                        attributionSourceState.getParcel());
+            }
             if (result != SUCCESS && result != ALREADY_EXISTS) {
                 Log.e(TAG, "Error code "+result+" when initializing Visualizer.");
                 switch (result) {
@@ -690,7 +695,7 @@
     private native final int native_setup(Object audioeffect_this,
                                           int audioSession,
                                           int[] id,
-                                          Identity identity);
+                                          @NonNull Parcel attributionSource);
 
     @GuardedBy("mStateLock")
     private native final void native_finalize();
diff --git a/media/java/android/media/permission/PermissionUtil.java b/media/java/android/media/permission/PermissionUtil.java
index 92fe882..b08d111d 100644
--- a/media/java/android/media/permission/PermissionUtil.java
+++ b/media/java/android/media/permission/PermissionUtil.java
@@ -51,24 +51,6 @@
  * @hide
  */
 public class PermissionUtil {
-    /**
-     * Create an identity for the current process and the passed context.
-     *
-     * @param context The process the identity is for. If {@code null}, the process's default
-     *                identity is chosen.
-     * @return The identity for the current process and context
-     */
-    public static @NonNull Identity myIdentity(@Nullable Context context) {
-        Identity identity = new Identity();
-
-        identity.pid = Process.myPid();
-        identity.uid = Process.myUid();
-        identity.packageName = context != null ? context.getOpPackageName()
-                : ActivityThread.currentOpPackageName();
-        identity.attributionTag = context != null ? context.getAttributionTag() : null;
-
-        return identity;
-    }
 
     /**
      * Authenticate an originator, where the binder call is coming from a middleman.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index d49790e..bc73f6a 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -51,6 +51,7 @@
     shared_libs: [
         "audioclient-types-aidl-cpp",
         "av-types-aidl-cpp",
+        "framework-permission-aidl-cpp",
         "libandroid_runtime",
         "libaudioclient",
         "libnativehelper",
@@ -85,7 +86,6 @@
         "android.hardware.drm@1.4",
         "android.hidl.memory@1.0",
         "android.hidl.token@1.0-utils",
-        "media_permission-aidl-cpp",
     ],
 
     header_libs: [
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index a360759..2636ab2 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -17,7 +17,6 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaPlayer-JNI"
-#include "permission_utils.h"
 #include "utils/Log.h"
 
 #include <media/mediaplayer.h>
@@ -80,8 +79,6 @@
 using namespace android;
 
 using media::VolumeShaper;
-using media::permission::Identity;
-using media::permission::convertIdentity;
 
 // ----------------------------------------------------------------------------
 
@@ -949,11 +946,14 @@
 
 static void
 android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
-                                       jobject jIdentity)
+                                       jobject jAttributionSource)
 {
     ALOGV("native_setup");
 
-    sp<MediaPlayer> mp = new MediaPlayer(convertIdentity(env, jIdentity));
+    Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
+    android::content::AttributionSourceState attributionSource;
+    attributionSource.readFromParcel(parcel);
+    sp<MediaPlayer> mp = new MediaPlayer(attributionSource);
     if (mp == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
@@ -1409,7 +1409,7 @@
     {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
     {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
     {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
-    {"native_setup",        "(Ljava/lang/Object;Landroid/media/permission/Identity;)V",(void *)android_media_MediaPlayer_native_setup},
+    {"native_setup",        "(Ljava/lang/Object;Landroid/os/Parcel;)V",(void *)android_media_MediaPlayer_native_setup},
     {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
     {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
     {"native_setAudioSessionId",   "(I)V",                      (void *)android_media_MediaPlayer_set_audio_session_id},
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 6641123..7ef0f77 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -24,7 +24,6 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaRecorderJNI"
-#include "permission_utils.h"
 #include <utils/Log.h>
 
 #include <gui/Surface.h>
@@ -46,13 +45,13 @@
 
 #include <system/audio.h>
 #include <android_runtime/android_view_Surface.h>
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
 
 // ----------------------------------------------------------------------------
 
 using namespace android;
 
-using android::media::permission::convertIdentity;
-
 // ----------------------------------------------------------------------------
 
 // helper function to extract a native Camera object from a Camera Java object
@@ -620,11 +619,14 @@
 
 static void
 android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
-                                         jstring packageName, jobject jIdentity)
+                                         jstring packageName, jobject jAttributionSource)
 {
     ALOGV("setup");
 
-    sp<MediaRecorder> mr = new MediaRecorder(convertIdentity(env, jIdentity));
+    Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
+    android::content::AttributionSourceState attributionSource;
+    attributionSource.readFromParcel(parcel);
+    sp<MediaRecorder> mr = new MediaRecorder(attributionSource);
 
     if (mr == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
@@ -871,7 +873,7 @@
     {"native_reset",         "()V",                             (void *)android_media_MediaRecorder_native_reset},
     {"release",              "()V",                             (void *)android_media_MediaRecorder_release},
     {"native_init",          "()V",                             (void *)android_media_MediaRecorder_native_init},
-    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Landroid/media/permission/Identity;)V",
+    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Landroid/os/Parcel;)V",
                                                                 (void *)android_media_MediaRecorder_native_setup},
     {"native_finalize",      "()V",                             (void *)android_media_MediaRecorder_native_finalize},
     {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index bfed983..2ddfacf 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -19,6 +19,7 @@
     ],
 
     shared_libs: [
+        "framework-permission-aidl-cpp",
         "liblog",
         "libcutils",
         "libutils",
@@ -27,11 +28,11 @@
         "libaudioclient",
         "libaudioutils",
         "libaudiofoundation",
-        "media_permission-aidl-cpp",
+        "libbinder"
     ],
 
     export_shared_lib_headers: [
-        "media_permission-aidl-cpp",
+        "framework-permission-aidl-cpp",
     ],
 
     version_script: "exports.lds",
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index 8a52456..84a8d51 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -28,14 +28,16 @@
 #include <cutils/bitops.h>
 #include <utils/Thread.h>
 
+#include <android/content/AttributionSourceState.h>
+
 #include "Visualizer.h"
 
 namespace android {
 
 // ---------------------------------------------------------------------------
 
-Visualizer::Visualizer (const Identity& identity)
-        :   AudioEffect(identity)
+Visualizer::Visualizer (const android::content::AttributionSourceState& attributionSource)
+        :   AudioEffect(attributionSource)
 {
 }
 
diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h
index 3ee91f0..aa07ce8 100644
--- a/media/jni/audioeffect/Visualizer.h
+++ b/media/jni/audioeffect/Visualizer.h
@@ -20,9 +20,7 @@
 #include <media/AudioEffect.h>
 #include <system/audio_effects/effect_visualizer.h>
 #include <utils/Thread.h>
-#include "android/media/permission/Identity.h"
-
-using namespace android::media::permission;
+#include "android/content/AttributionSourceState.h"
 
 /**
  * The Visualizer class enables application to retrieve part of the currently playing audio for
@@ -68,9 +66,9 @@
     /* Constructor.
      * See AudioEffect constructor for details on parameters.
      */
-                        explicit Visualizer(const Identity& identity);
+     explicit Visualizer(const android::content::AttributionSourceState& attributionSource);
 
-                        ~Visualizer();
+     ~Visualizer();
 
     /**
      * Initialize an uninitialized Visualizer.
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 953b7e0..3a8decd 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -25,7 +25,9 @@
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include "media/AudioEffect.h"
-#include "permission_utils.h"
+
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
 
 #include <nativehelper/ScopedUtfChars.h>
 
@@ -35,8 +37,6 @@
 
 using namespace android;
 
-using media::permission::convertIdentity;
-
 #define AUDIOEFFECT_SUCCESS                      0
 #define AUDIOEFFECT_ERROR                       (-1)
 #define AUDIOEFFECT_ERROR_ALREADY_EXISTS        (-2)
@@ -273,7 +273,7 @@
 android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
         jstring type, jstring uuid, jint priority, jint sessionId,
         jint deviceType, jstring deviceAddress,
-        jintArray jId, jobjectArray javadesc, jobject jIdentity, jboolean probe)
+        jintArray jId, jobjectArray javadesc, jobject jAttributionSource, jboolean probe)
 {
     ALOGV("android_media_AudioEffect_native_setup");
     AudioEffectJniStorage* lpJniStorage = NULL;
@@ -285,6 +285,8 @@
     effect_descriptor_t desc;
     jobject jdesc;
     AudioDeviceTypeAddr device;
+    AttributionSourceState attributionSource;
+    Parcel* parcel = NULL;
 
     setAudioEffect(env, thiz, 0);
 
@@ -338,7 +340,9 @@
     }
 
     // create the native AudioEffect object
-    lpAudioEffect = new AudioEffect(convertIdentity(env, jIdentity));
+    parcel = parcelForJavaObject(env, jAttributionSource);
+    attributionSource.readFromParcel(parcel);
+    lpAudioEffect = new AudioEffect(attributionSource);
     if (lpAudioEffect == 0) {
         ALOGE("Error creating AudioEffect");
         goto setup_failure;
@@ -774,7 +778,7 @@
 // Dalvik VM type signatures
 static const JNINativeMethod gMethods[] = {
     {"native_init",          "()V",      (void *)android_media_AudioEffect_native_init},
-    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Landroid/media/permission/Identity;Z)I",
+    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Landroid/os/Parcel;Z)I",
                                          (void *)android_media_AudioEffect_native_setup},
     {"native_finalize",      "()V",      (void *)android_media_AudioEffect_native_finalize},
     {"native_release",       "()V",      (void *)android_media_AudioEffect_native_release},
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 439715c..b30f00f 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -25,12 +25,16 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/threads.h>
 #include "Visualizer.h"
-#include "permission_utils.h"
 
 #include <nativehelper/ScopedUtfChars.h>
 
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
+
 using namespace android;
 
+using content::AttributionSourceState;
+
 #define VISUALIZER_SUCCESS                      0
 #define VISUALIZER_ERROR                       (-1)
 #define VISUALIZER_ERROR_ALREADY_EXISTS        (-2)
@@ -348,13 +352,15 @@
 
 static jint
 android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
-        jint sessionId, jintArray jId, jobject jIdentity)
+        jint sessionId, jintArray jId, jobject jAttributionSource)
 {
     ALOGV("android_media_visualizer_native_setup");
     VisualizerJniStorage* lpJniStorage = NULL;
     int lStatus = VISUALIZER_ERROR_NO_MEMORY;
     sp<Visualizer> lpVisualizer;
     jint* nId = NULL;
+    AttributionSourceState attributionSource;
+    Parcel* parcel = nullptr;
 
     setVisualizer(env, thiz, 0);
 
@@ -381,7 +387,9 @@
     }
 
     // create the native Visualizer object
-    lpVisualizer = new Visualizer(convertIdentity(env, jIdentity));
+    parcel = parcelForJavaObject(env, jAttributionSource);
+    attributionSource.readFromParcel(parcel);
+    lpVisualizer = sp<Visualizer>::make(attributionSource);
     if (lpVisualizer == 0) {
         ALOGE("Error creating Visualizer");
         goto setup_failure;
@@ -678,7 +686,7 @@
 // Dalvik VM type signatures
 static const JNINativeMethod gMethods[] = {
     {"native_init",            "()V",     (void *)android_media_visualizer_native_init},
-    {"native_setup",           "(Ljava/lang/Object;I[ILandroid/media/permission/Identity;)I",
+    {"native_setup",           "(Ljava/lang/Object;I[ILandroid/os/Parcel;)I",
                                           (void *)android_media_visualizer_native_setup},
     {"native_finalize",          "()V",   (void *)android_media_visualizer_native_finalize},
     {"native_release",           "()V",   (void *)android_media_visualizer_native_release},
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
index 4227cd8..ee473f5 100644
--- a/media/jni/soundpool/Android.bp
+++ b/media/jni/soundpool/Android.bp
@@ -126,6 +126,7 @@
     ],
 
     shared_libs: [
+        "framework-permission-aidl-cpp",
         "libaudioutils",
         "liblog",
         "libcutils",
@@ -135,7 +136,6 @@
         "libaudioclient",
         "libmediandk",
         "libbinder",
-        "media_permission-aidl-cpp",
     ],
 
     cflags: [
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index 95fe000..bbbef38 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -17,7 +17,7 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "SoundPool::Stream"
 #include <utils/Log.h>
-#include<android/media/permission/Identity.h>
+#include <android/content/AttributionSourceState.h>
 
 #include "Stream.h"
 
@@ -25,8 +25,6 @@
 
 namespace android::soundpool {
 
-using media::permission::Identity;
-
 Stream::~Stream()
 {
     ALOGV("%s(%p)", __func__, this);
@@ -330,15 +328,16 @@
 
             // do not create a new audio track if current track is compatible with sound parameters
 
-            Identity identity = Identity();
-            identity.packageName = mStreamManager->getOpPackageName();
+            android::content::AttributionSourceState attributionSource;
+            attributionSource.packageName = mStreamManager->getOpPackageName();
+            attributionSource.token = sp<BBinder>::make();
             // TODO b/182469354 make consistent with AudioRecord, add util for native source
             newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
                     channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
                     staticCallback, userData,
                     0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
                     AudioTrack::TRANSFER_DEFAULT,
-                    nullptr /*offloadInfo*/, identity,
+                    nullptr /*offloadInfo*/, attributionSource,
                     mStreamManager->getAttributes(),
                     false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/);
             // Set caller name so it can be logged in destructor.
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index f8dd384..5904589 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -19,5 +19,5 @@
     ],
 
     sdk_version: "system_current",
-    min_sdk_version: "21",
+    min_sdk_version: "28",
 }
diff --git a/packages/SettingsLib/IllustrationPreference/res/drawable/protection_background.xml b/packages/SettingsLib/IllustrationPreference/res/drawable/protection_background.xml
index dd2fa5e..0734aca 100644
--- a/packages/SettingsLib/IllustrationPreference/res/drawable/protection_background.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/drawable/protection_background.xml
@@ -16,11 +16,7 @@
 -->
 
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:top="@dimen/settingslib_illustration_padding"
-        android:left="@dimen/settingslib_illustration_padding"
-        android:right="@dimen/settingslib_illustration_padding"
-        android:bottom="@dimen/settingslib_illustration_padding">
+    <item>
         <shape android:shape="rectangle">
             <solid android:color="@color/settingslib_protection_color"/>
             <corners android:radius="28dp"/>
diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
index 7d65aae..54731f5 100644
--- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
@@ -23,14 +23,33 @@
     android:gravity="center"
     android:orientation="horizontal">
 
-    <com.airbnb.lottie.LottieAnimationView
-        android:id="@+id/lottie_view"
-        android:layout_width="412dp"
+    <LinearLayout
+        android:layout_width="match_parent"
         android:layout_height="300dp"
         android:layout_gravity="center"
-        android:clipToOutline="true"
-        android:background="@drawable/protection_background"
-        android:importantForAccessibility="no"/>
+        android:gravity="center_vertical"
+        android:padding="@dimen/settingslib_illustration_padding"
+        android:orientation="vertical">
+        <com.airbnb.lottie.LottieAnimationView
+            android:id="@+id/lottie_view"
+            android:adjustViewBounds="true"
+            android:maxWidth="412dp"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:clipToOutline="true"
+            android:background="@drawable/protection_background"
+            android:importantForAccessibility="no"/>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/middleground_layout"
+        android:layout_width="412dp"
+        android:layout_height="300dp"
+        android:padding="@dimen/settingslib_illustration_padding"
+        android:background="@android:color/transparent"
+        android:layout_gravity="center"
+        android:visibility="gone"/>
 
     <ImageView
         android:id="@+id/video_play_button"
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
index e53a43e..ead5174 100644
--- a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
@@ -17,4 +17,46 @@
 
 <resources>
     <color name="settingslib_protection_color">@android:color/white</color>
+
+    <!-- Dynamic colors-->
+    <color name="settingslib_color_blue600">#1a73e8</color>
+    <color name="settingslib_color_blue400">#669df6</color>
+    <color name="settingslib_color_blue300">#8ab4f8</color>
+    <color name="settingslib_color_blue100">#d2e3fc</color>
+    <color name="settingslib_color_blue50">#e8f0fe</color>
+    <color name="settingslib_color_green600">#1e8e3e</color>
+    <color name="settingslib_color_green400">#5bb974</color>
+    <color name="settingslib_color_green100">#ceead6</color>
+    <color name="settingslib_color_green50">#e6f4ea</color>
+    <color name="settingslib_color_red600">#d93025</color>
+    <color name="settingslib_color_red400">#ee675c</color>
+    <color name="settingslib_color_red100">#fad2cf</color>
+    <color name="settingslib_color_red50">#fce8e6</color>
+    <color name="settingslib_color_yellow600">#f9ab00</color>
+    <color name="settingslib_color_yellow400">#fcc934</color>
+    <color name="settingslib_color_yellow100">#feefc3</color>
+    <color name="settingslib_color_yellow50">#fef7e0</color>
+    <color name="settingslib_color_grey900">#202124</color>
+    <color name="settingslib_color_grey800">#3c4043</color>
+    <color name="settingslib_color_grey700">#5f6368</color>
+    <color name="settingslib_color_grey600">#80868b</color>
+    <color name="settingslib_color_grey400">#bdc1c6</color>
+    <color name="settingslib_color_grey300">#dadce0</color>
+    <color name="settingslib_color_grey200">#e8eaed</color>
+    <color name="settingslib_color_orange600">#e8710a</color>
+    <color name="settingslib_color_orange400">#fa903e</color>
+    <color name="settingslib_color_orange300">#fcad70</color>
+    <color name="settingslib_color_orange100">#fedfc8</color>
+    <color name="settingslib_color_pink600">#e52592</color>
+    <color name="settingslib_color_pink400">#ff63b8</color>
+    <color name="settingslib_color_pink300">#ff8bcb</color>
+    <color name="settingslib_color_pink100">#fdcfe8</color>
+    <color name="settingslib_color_purple600">#9334e6</color>
+    <color name="settingslib_color_purple400">#af5cf7</color>
+    <color name="settingslib_color_purple300">#c58af9</color>
+    <color name="settingslib_color_purple100">#e9d2fd</color>
+    <color name="settingslib_color_cyan600">#12b5c8</color>
+    <color name="settingslib_color_cyan400">#4ecde6</color>
+    <color name="settingslib_color_cyan300">#78d9ec</color>
+    <color name="settingslib_color_cyan100">#cbf0f8</color>
 </resources>
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/dimens.xml b/packages/SettingsLib/IllustrationPreference/res/values/dimens.xml
index 79562b9..5e540d3 100644
--- a/packages/SettingsLib/IllustrationPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/values/dimens.xml
@@ -17,5 +17,5 @@
 
 <resources>
     <!-- Padding of illustration -->
-    <dimen name="settingslib_illustration_padding">16dp</dimen>
+    <dimen name="settingslib_illustration_padding">12dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java
new file mode 100644
index 0000000..cd2ddeb
--- /dev/null
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.util.Pair;
+
+import com.airbnb.lottie.LottieAnimationView;
+import com.airbnb.lottie.LottieProperty;
+import com.airbnb.lottie.model.KeyPath;
+import com.airbnb.lottie.value.LottieFrameInfo;
+import com.airbnb.lottie.value.SimpleLottieValueCallback;
+
+import java.util.HashMap;
+
+/**
+ * ColorUtils is a util class which help the lottie illustration
+ * changes the color of tags in the json file.
+ */
+
+public class ColorUtils {
+
+    private static HashMap<String, Integer> sSysColors;
+    static {
+        sSysColors = new HashMap<>();
+        sSysColors.put(".colorAccent", android.R.attr.colorAccent);
+        sSysColors.put(".colorPrimary", android.R.attr.colorPrimary);
+        sSysColors.put(".colorPrimaryDark", android.R.attr.colorPrimaryDark);
+        sSysColors.put(".colorSecondary", android.R.attr.colorSecondary);
+    }
+
+    private static HashMap<String, Pair<Integer, Integer>> sFixedColors;
+    static {
+        sFixedColors = new HashMap<>();
+        sFixedColors.put(".blue600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_blue600, R.color.settingslib_color_blue400));
+        sFixedColors.put(".green600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_green600, R.color.settingslib_color_green400));
+        sFixedColors.put(".red600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_red600, R.color.settingslib_color_red400));
+        sFixedColors.put(".yellow600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_yellow600, R.color.settingslib_color_yellow400));
+        sFixedColors.put(".blue400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_blue400, R.color.settingslib_color_blue100));
+        sFixedColors.put(".green400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_green400, R.color.settingslib_color_green100));
+        sFixedColors.put(".red400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_red400, R.color.settingslib_color_red100));
+        sFixedColors.put(".yellow400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_yellow400, R.color.settingslib_color_yellow100));
+        sFixedColors.put(".blue300", new Pair<Integer, Integer>(
+                R.color.settingslib_color_blue300, R.color.settingslib_color_blue50));
+        sFixedColors.put(".blue50", new Pair<Integer, Integer>(
+                R.color.settingslib_color_blue50, R.color.settingslib_color_grey900));
+        sFixedColors.put(".green50", new Pair<Integer, Integer>(
+                R.color.settingslib_color_green50, R.color.settingslib_color_grey900));
+        sFixedColors.put(".red50", new Pair<Integer, Integer>(
+                R.color.settingslib_color_red50, R.color.settingslib_color_grey900));
+        sFixedColors.put(".yellow50", new Pair<Integer, Integer>(
+                R.color.settingslib_color_yellow50, R.color.settingslib_color_grey900));
+        // Secondary colors
+        sFixedColors.put(".orange600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_orange600, R.color.settingslib_color_orange300));
+        sFixedColors.put(".pink600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_pink600, R.color.settingslib_color_pink300));
+        sFixedColors.put(".purple600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_purple600, R.color.settingslib_color_purple300));
+        sFixedColors.put(".cyan600", new Pair<Integer, Integer>(
+                R.color.settingslib_color_cyan600, R.color.settingslib_color_cyan300));
+        sFixedColors.put(".orange400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_orange400, R.color.settingslib_color_orange100));
+        sFixedColors.put(".pink400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_pink400, R.color.settingslib_color_pink100));
+        sFixedColors.put(".purple400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_purple400, R.color.settingslib_color_purple100));
+        sFixedColors.put(".cyan400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_cyan400, R.color.settingslib_color_cyan100));
+        sFixedColors.put(".gery400", new Pair<Integer, Integer>(
+                R.color.settingslib_color_grey400, R.color.settingslib_color_grey700));
+        sFixedColors.put(".gery300", new Pair<Integer, Integer>(
+                R.color.settingslib_color_grey300, R.color.settingslib_color_grey600));
+        sFixedColors.put(".gery200", new Pair<Integer, Integer>(
+                R.color.settingslib_color_grey200, R.color.settingslib_color_grey800));
+    }
+
+    private static boolean isDarkMode(Context context) {
+        return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES;
+    }
+
+    /**
+     * Apply the color of tags to the animation.
+     */
+    public static void applyDynamicColors(Context context, LottieAnimationView animationView) {
+        for (String key : sSysColors.keySet()) {
+            final int color = sSysColors.get(key);
+            animationView.addValueCallback(
+                    new KeyPath("**", key, "**"),
+                    LottieProperty.COLOR_FILTER,
+                    new SimpleLottieValueCallback<ColorFilter>() {
+                        @Override
+                        public ColorFilter getValue(LottieFrameInfo<ColorFilter> frameInfo) {
+                            return new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
+                        }
+                    }
+            );
+        }
+        for (String key : sFixedColors.keySet()) {
+            final Pair<Integer, Integer> fixedColorPair = sFixedColors.get(key);
+            final int color = isDarkMode(context) ? fixedColorPair.second : fixedColorPair.first;
+            animationView.addValueCallback(
+                    new KeyPath("**", key, "**"),
+                    LottieProperty.COLOR_FILTER,
+                    new SimpleLottieValueCallback<ColorFilter>() {
+                        @Override
+                        public ColorFilter getValue(LottieFrameInfo<ColorFilter> frameInfo) {
+                            return new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
+                        }
+                    }
+            );
+        }
+    }
+}
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 90b8a32..7b2a0af 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -23,6 +23,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import androidx.annotation.VisibleForTesting;
@@ -38,10 +39,14 @@
 public class IllustrationPreference extends Preference implements OnPreferenceClickListener {
 
     static final String TAG = "IllustrationPreference";
+
     private int mAnimationId;
     private boolean mIsAnimating;
+    private boolean mIsAutoScale;
     private ImageView mPlayButton;
     private LottieAnimationView mIllustrationView;
+    private View mMiddleGroundView;
+    private FrameLayout mMiddleGroundLayout;
 
     public IllustrationPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -66,13 +71,20 @@
             Log.w(TAG, "Invalid illustration resource id.");
             return;
         }
+        mMiddleGroundLayout = (FrameLayout) holder.findViewById(R.id.middleground_layout);
         mPlayButton = (ImageView) holder.findViewById(R.id.video_play_button);
         mIllustrationView = (LottieAnimationView) holder.findViewById(R.id.lottie_view);
         mIllustrationView.setAnimation(mAnimationId);
         mIllustrationView.loop(true);
+        ColorUtils.applyDynamicColors(getContext(), mIllustrationView);
         mIllustrationView.playAnimation();
         updateAnimationStatus(mIsAnimating);
-        setOnPreferenceClickListener(this);
+        if (mIsAutoScale) {
+            enableAnimationAutoScale(mIsAutoScale);
+        }
+        if (mMiddleGroundView != null) {
+            enableMiddleGroundView();
+        }
     }
 
     @Override
@@ -102,16 +114,59 @@
         return mIllustrationView.isAnimating();
     }
 
+    /**
+     * Set the middle ground view to preference. The user
+     * can overlay a view on top of the animation.
+     */
+    public void setMiddleGroundView(View view) {
+        mMiddleGroundView = view;
+        if (mMiddleGroundLayout == null) {
+            return;
+        }
+        enableMiddleGroundView();
+    }
+
+    /**
+     * Remove the middle ground view of preference.
+     */
+    public void removeMiddleGroundView() {
+        if (mMiddleGroundLayout == null) {
+            return;
+        }
+        mMiddleGroundLayout.removeAllViews();
+        mMiddleGroundLayout.setVisibility(View.GONE);
+    }
+
+    /**
+     * Enables the auto scale feature of animation view.
+     */
+    public void enableAnimationAutoScale(boolean enable) {
+        mIsAutoScale = enable;
+        if (mIllustrationView == null) {
+            return;
+        }
+        mIllustrationView.setScaleType(
+                mIsAutoScale ? ImageView.ScaleType.CENTER_CROP : ImageView.ScaleType.CENTER_INSIDE);
+    }
+
+    private void enableMiddleGroundView() {
+        mMiddleGroundLayout.removeAllViews();
+        mMiddleGroundLayout.addView(mMiddleGroundView);
+        mMiddleGroundLayout.setVisibility(View.VISIBLE);
+    }
+
     private void init(Context context, AttributeSet attrs) {
         setLayoutResource(R.layout.illustration_preference);
 
         mIsAnimating = true;
+        mIsAutoScale = false;
         if (attrs != null) {
             final TypedArray a = context.obtainStyledAttributes(attrs,
                     R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
             mAnimationId = a.getResourceId(R.styleable.LottieAnimationView_lottie_rawRes, 0);
             a.recycle();
         }
+        setOnPreferenceClickListener(this);
     }
 
     private void updateAnimationStatus(boolean playAnimation) {
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index 2d214fe..f9e9eab 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -58,6 +58,8 @@
             android:layout_width="wrap_content"
             android:layout_height="48dp"
             android:layout_gravity="center_vertical"
+            android:focusable="false"
+            android:clickable="false"
             android:track="@drawable/settingslib_track_selector"
             android:thumb="@drawable/settingslib_thumb_selector"
             android:theme="@style/MainSwitch.Settingslib"/>
diff --git a/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
index 4612861..a6eeb88 100644
--- a/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
+++ b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
@@ -25,6 +25,7 @@
 import android.view.animation.Interpolator;
 
 import androidx.core.os.BuildCompat;
+import androidx.fragment.app.Fragment;
 
 import com.google.android.material.transition.platform.FadeThroughProvider;
 import com.google.android.material.transition.platform.MaterialSharedAxis;
@@ -92,7 +93,7 @@
      * triggered when the page is launched/entering.
      */
     public static void applyForwardTransition(Activity activity) {
-        if (!BuildCompat.isAtLeastS()) {
+        if (!isSettingsTransitionEnabled()) {
             return;
         }
         if (activity == null) {
@@ -118,7 +119,7 @@
      * previously-started Activity.
      */
     public static void applyBackwardTransition(Activity activity) {
-        if (!BuildCompat.isAtLeastS()) {
+        if (!isSettingsTransitionEnabled()) {
             return;
         }
         if (activity == null) {
@@ -134,4 +135,49 @@
         window.setReturnTransition(backward);
         window.setReenterTransition(backward);
     }
+
+    /**
+     * Apply the forward transition to the {@link Fragment}, including Exit Transition and Enter
+     * Transition.
+     *
+     * The Exit Transition takes effect when leaving the page, while the Enter Transition is
+     * triggered when the page is launched/entering.
+     */
+    public static void applyForwardTransition(Fragment fragment) {
+        if (!isSettingsTransitionEnabled()) {
+            return;
+        }
+        if (fragment == null) {
+            Log.w(TAG, "applyForwardTransition: Invalid fragment!");
+            return;
+        }
+        final MaterialSharedAxis forward = createSettingsSharedAxis(fragment.getContext(), true);
+        fragment.setExitTransition(forward);
+        fragment.setEnterTransition(forward);
+    }
+
+    /**
+     * Apply the backward transition to the {@link Fragment}, including Return Transition and
+     * Reenter Transition.
+     *
+     * Return Transition will be used to move Views out of the scene when the Window is preparing
+     * to close. Reenter Transition will be used to move Views in to the scene when returning from a
+     * previously-started Fragment.
+     */
+    public static void applyBackwardTransition(Fragment fragment) {
+        if (!isSettingsTransitionEnabled()) {
+            return;
+        }
+        if (fragment == null) {
+            Log.w(TAG, "applyBackwardTransition: Invalid fragment!");
+            return;
+        }
+        final MaterialSharedAxis backward = createSettingsSharedAxis(fragment.getContext(), false);
+        fragment.setReturnTransition(backward);
+        fragment.setReenterTransition(backward);
+    }
+
+    private static boolean isSettingsTransitionEnabled() {
+        return BuildCompat.isAtLeastS();
+    }
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 43427d3..4caced6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -886,6 +886,9 @@
     <!-- UI debug setting: force right to left layout summary [CHAR LIMIT=100] -->
     <string name="force_rtl_layout_all_locales_summary">Force screen layout direction to RTL for all locales</string>
 
+    <!-- UI debug setting: enable or disable window blurs [CHAR LIMIT=25] -->
+    <string name="window_blurs">Allow window-level blurs</string>
+
     <!-- UI debug setting: force anti-aliasing to render apps [CHAR LIMIT=25] -->
     <string name="force_msaa">Force 4x MSAA</string>
     <!-- UI debug setting: force anti-aliasing summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 6cf53d0b..6b1e282 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -15,6 +15,7 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK;
 import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_DOCK;
@@ -31,6 +32,7 @@
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 
+import android.annotation.TargetApi;
 import android.app.Notification;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -38,6 +40,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -84,12 +87,14 @@
     public void startScan() {
         mMediaDevices.clear();
         mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+        mRouterManager.startScan();
         refreshDevices();
     }
 
     @Override
     public void stopScan() {
         mRouterManager.unregisterCallback(mMediaRouterCallback);
+        mRouterManager.stopScan();
     }
 
     /**
@@ -391,6 +396,38 @@
         return shouldDisableMediaOutput;
     }
 
+    @TargetApi(Build.VERSION_CODES.R)
+    boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
+        if (sessionInfo == null) {
+            Log.w(TAG, "shouldEnableVolumeSeekBar() package name is null or empty!");
+            return false;
+        }
+        final List<MediaRoute2Info> mediaRoute2Infos =
+                mRouterManager.getSelectedRoutes(sessionInfo);
+        // More than one selected route
+        if (mediaRoute2Infos.size() > 1) {
+            if (DEBUG) {
+                Log.d(TAG, "shouldEnableVolumeSeekBar() package name : "
+                        + sessionInfo.getClientPackageName()
+                        + ", mediaRoute2Infos.size() " + mediaRoute2Infos.size());
+            }
+            return false;
+        }
+        // Route contains group feature
+        for (MediaRoute2Info mediaRoute2Info : mediaRoute2Infos) {
+            final List<String> features = mediaRoute2Info.getFeatures();
+            if (features.contains(FEATURE_REMOTE_GROUP_PLAYBACK)) {
+                if (DEBUG) {
+                    Log.d(TAG, "shouldEnableVolumeSeekBar() package name : "
+                            + mediaRoute2Info.getClientPackageName()
+                            + "contain group playback ");
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void refreshDevices() {
         mMediaDevices.clear();
         mCurrentConnectedDevice = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 1f3e0e9..a8da2c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -408,6 +408,13 @@
         return mInfoMediaManager.shouldDisableMediaOutput(packageName);
     }
 
+    /**
+     * Returns {@code true} if needed to enable volume seekbar, otherwise returns {@code false}.
+     */
+    public boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
+        return mInfoMediaManager.shouldEnableVolumeSeekBar(sessionInfo);
+    }
+
     @VisibleForTesting
     MediaDevice updateCurrentConnectedDevice() {
         MediaDevice connectedDevice = null;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index 4a14403..f197cbb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -22,6 +22,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 
 import com.airbnb.lottie.LottieAnimationView;
 
@@ -41,14 +44,15 @@
     @Mock
     LottieAnimationView mAnimationView;
 
+    private Context mContext;
     private IllustrationPreference mPreference;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        final Context context = RuntimeEnvironment.application;
+        mContext = RuntimeEnvironment.application;
         final AttributeSet attributeSet = Robolectric.buildAttributeSet().build();
-        mPreference = new IllustrationPreference(context, attributeSet);
+        mPreference = new IllustrationPreference(mContext, attributeSet);
         ReflectionHelpers.setField(mPreference, "mIllustrationView", mAnimationView);
     }
 
@@ -65,4 +69,27 @@
 
         assertThat(mPreference.isAnimating()).isTrue();
     }
+
+    @Test
+    public void setMiddleGroundView_middleGroundView_shouldVisible() {
+        final View view = new View(mContext);
+        final FrameLayout layout = new FrameLayout(mContext);
+        layout.setVisibility(View.GONE);
+        ReflectionHelpers.setField(mPreference, "mMiddleGroundView", view);
+        ReflectionHelpers.setField(mPreference, "mMiddleGroundLayout", layout);
+
+        mPreference.setMiddleGroundView(view);
+
+        assertThat(layout.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void enableAnimationAutoScale_shouldChangeScaleType() {
+        final LottieAnimationView animationView = new LottieAnimationView(mContext);
+        ReflectionHelpers.setField(mPreference, "mIllustrationView", animationView);
+
+        mPreference.enableAnimationAutoScale(true);
+
+        assertThat(animationView.getScaleType()).isEqualTo(ImageView.ScaleType.CENTER_CROP);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
index 701a343..0845ca3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
@@ -76,6 +76,14 @@
     }
 
     @Test
+    public void getSwitch_shouldNotFocusableAndClickable() {
+        final Switch switchObj = mBar.getSwitch();
+
+        assertThat(switchObj.isFocusable()).isFalse();
+        assertThat(switchObj.isClickable()).isFalse();
+    }
+
+    @Test
     public void show_shouldVisible() {
         mBar.show();
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index e1f72c1..457e8e6 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -99,8 +99,10 @@
      * @param forNotification If we want the alpha of the notification shade or the scrim.
      */
     public static float getNotificationScrimAlpha(float fraction, boolean forNotification) {
-        if (!forNotification) {
-            fraction = MathUtils.saturate(1.7f * fraction);
+        if (forNotification) {
+            fraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction);
+        } else {
+            fraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction);
         }
         fraction = fraction * 1.2f - 0.2f;
         if (fraction <= 0) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index eff412e..423bd56 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -21,7 +21,6 @@
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.view.Surface;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -30,44 +29,25 @@
  */
 public class LockIconView extends ImageView {
     @NonNull private final RectF mSensorRect;
-    @NonNull private final Context mContext;
-
     @NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
     private int mRadius;
 
     public LockIconView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mContext = context;
         mSensorRect = new RectF();
     }
 
     void setLocation(@NonNull PointF center, int radius) {
         mLockIconCenter = center;
         mRadius = radius;
-    }
 
-    // The "h" and "w" are the display's height and width relative to its current rotation.
-    private void updateSensorRect(int h, int w) {
-        // mSensorProps coordinates assume portrait mode.
+        // mSensorProps coordinates assume portrait mode which is OK b/c the keyguard is always in
+        // portrait.
         mSensorRect.set(mLockIconCenter.x - mRadius,
                 mLockIconCenter.y - mRadius,
                 mLockIconCenter.x + mRadius,
                 mLockIconCenter.y + mRadius);
 
-        // Transform mSensorRect if the device is in landscape mode.
-        switch (mContext.getDisplay().getRotation()) {
-            case Surface.ROTATION_90:
-                mSensorRect.set(mSensorRect.top, h - mSensorRect.right, mSensorRect.bottom,
-                        h - mSensorRect.left);
-                break;
-            case Surface.ROTATION_270:
-                mSensorRect.set(w - mSensorRect.bottom, mSensorRect.left, w - mSensorRect.top,
-                        mSensorRect.right);
-                break;
-            default:
-                // Do nothing to stay in portrait mode.
-        }
-
         setX(mSensorRect.left);
         setY(mSensorRect.top);
         setLayoutParams(new FrameLayout.LayoutParams(
@@ -76,18 +56,6 @@
     }
 
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        // Always re-compute the layout regardless of whether "changed" is true. It is usually false
-        // when the device goes from landscape to seascape and vice versa, but mSensorRect and
-        // its dependencies need to be recalculated to stay at the same physical location on the
-        // screen.
-        final int w = getLayoutParams().width;
-        final int h = getLayoutParams().height;
-        updateSensorRect(h, w);
-    }
-
-    @Override
     public boolean hasOverlappingRendering() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index e6fe060..a28223d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -117,7 +117,8 @@
                     .setAppPairs(mWMComponent.getAppPairs())
                     .setTaskViewFactory(mWMComponent.getTaskViewFactory())
                     .setTransitions(mWMComponent.getTransitions())
-                    .setStartingSurface(mWMComponent.getStartingSurface());
+                    .setStartingSurface(mWMComponent.getStartingSurface())
+                    .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper());
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
             // is separating this logic into newly creating SystemUITestsFactory.
@@ -132,8 +133,8 @@
                     .setAppPairs(Optional.ofNullable(null))
                     .setTaskViewFactory(Optional.ofNullable(null))
                     .setTransitions(Transitions.createEmptyForTesting())
-                    .setStartingSurface(Optional.ofNullable(null));
-
+                    .setStartingSurface(Optional.ofNullable(null))
+                    .setTaskSurfaceHelper(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
         if (mInitializeComponents) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 2e6c9e4..bfb7105 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -75,6 +75,7 @@
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
     private final LayoutParams mParams;
+    private int mWindowHeight;
     @VisibleForTesting
     final Rect mDraggableWindowBounds = new Rect();
     private boolean mIsVisible = false;
@@ -94,6 +95,7 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
         mParams = createLayoutParams(context);
+        mWindowHeight = mWindowManager.getCurrentWindowMetrics().getBounds().height();
         mImageView = imageView;
         mImageView.setOnTouchListener(this::onTouch);
         mImageView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@@ -310,6 +312,16 @@
     }
 
     void onConfigurationChanged(int configDiff) {
+        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+            mDraggableWindowBounds.set(getDraggableWindowBounds());
+            // Keep the Y position with the same height ratio before the window height is changed.
+            final int windowHeight = mWindowManager.getCurrentWindowMetrics().getBounds().height();
+            final float windowHeightFraction = (float) mParams.y / mWindowHeight;
+            mParams.y = (int) (windowHeight * windowHeightFraction);
+            mWindowHeight = windowHeight;
+            stickToScreenEdge(mToLeftScreenEdge);
+            return;
+        }
         if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
             applyResourcesValuesWithDensityChanged();
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
index 4f8d866..c941d66 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
@@ -21,6 +21,7 @@
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.provider.Settings;
 
 import androidx.annotation.NonNull;
@@ -75,7 +76,7 @@
         if (mListeners.size() == 1) {
             mContentResolver.registerContentObserver(
                     Settings.Secure.getUriFor(mKey), /* notifyForDescendants= */
-                    false, mContentObserver);
+                    false, mContentObserver, UserHandle.USER_ALL);
         }
     }
 
@@ -100,7 +101,7 @@
      * See {@link Settings.Secure}.
      */
     public final String getSettingsValue() {
-        return Settings.Secure.getString(mContentResolver, mKey);
+        return Settings.Secure.getStringForUser(mContentResolver, mKey, UserHandle.USER_CURRENT);
     }
 
     private void updateValueChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 112e9ca..7cd43ef 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -20,11 +20,12 @@
 
 import android.content.Context;
 import android.text.TextUtils;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.MainThread;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -37,41 +38,65 @@
 @SysUISingleton
 public class AccessibilityFloatingMenuController implements
         AccessibilityButtonModeObserver.ModeChangedListener,
-        AccessibilityButtonTargetsObserver.TargetsChangedListener,
-        AccessibilityManager.AccessibilityStateChangeListener {
+        AccessibilityButtonTargetsObserver.TargetsChangedListener {
 
     private final Context mContext;
-    private final AccessibilityManager mAccessibilityManager;
     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @VisibleForTesting
     IAccessibilityFloatingMenu mFloatingMenu;
     private int mBtnMode;
     private String mBtnTargets;
+    private boolean mIsKeyguardVisible;
+    private boolean mIsAccessibilityManagerServiceReady;
+
+    @VisibleForTesting
+    final KeyguardUpdateMonitorCallback mKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+        // Accessibility floating menu needs to retrieve information from
+        // AccessibilityManagerService, and it would be ready before onUserUnlocked().
+        @Override
+        public void onUserUnlocked() {
+            mIsAccessibilityManagerServiceReady = true;
+            handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
+        }
+
+        // Keyguard state would be changed before AccessibilityManagerService is ready to retrieve,
+        // need to wait until receive onUserUnlocked().
+        @Override
+        public void onKeyguardVisibilityChanged(boolean showing) {
+            mIsKeyguardVisible = showing;
+            if (mIsAccessibilityManagerServiceReady) {
+                handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
+            }
+        }
+
+        @Override
+        public void onUserSwitching(int userId) {
+            destroyFloatingMenu();
+        }
+
+        @Override
+        public void onUserSwitchComplete(int userId) {
+            mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+            mBtnTargets =
+                    mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
+            handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
+        }
+    };
 
     @Inject
     public AccessibilityFloatingMenuController(Context context,
             AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
-            AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
+            AccessibilityButtonModeObserver accessibilityButtonModeObserver,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mContext = context;
         mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
-        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
 
-        mAccessibilityButtonModeObserver.addListener(this);
-        mAccessibilityButtonTargetsObserver.addListener(this);
-        mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
-        mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
-
-        // Accessibility floating menu widget needs accessibility service to work, but system
-        // accessibility might be unavailable during the phone get booted, hence it needs to wait
-        // for accessibility manager callback to work.
-        mAccessibilityManager.addAccessibilityStateChangeListener(this);
-        if (mAccessibilityManager.isEnabled()) {
-            handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
-            mAccessibilityManager.removeAccessibilityStateChangeListener(this);
-        }
+        init();
     }
 
     /**
@@ -82,7 +107,7 @@
     @Override
     public void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode) {
         mBtnMode = mode;
-        handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
+        handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
     }
 
     /**
@@ -94,27 +119,39 @@
     @Override
     public void onAccessibilityButtonTargetsChanged(String targets) {
         mBtnTargets = targets;
-        handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
+        handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
+    }
+
+    private void init() {
+        mIsKeyguardVisible = false;
+        mIsAccessibilityManagerServiceReady = false;
+        mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+        mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
+        registerContentObservers();
+    }
+
+    private void registerContentObservers() {
+        mAccessibilityButtonModeObserver.addListener(this);
+        mAccessibilityButtonTargetsObserver.addListener(this);
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
     }
 
     /**
-     * Handles visibility of the accessibility floating menu when system accessibility state
-     * changes.
-     * If system accessibility become available onAccessibilityStateChanged(true), then we don't
-     * need to listen to this listener anymore.
+     * Handles the accessibility floating menu visibility with the given values.
      *
-     * @param enabled Whether accessibility is enabled.
+     * @param keyguardVisible the keyguard visibility status. Not show the
+     *                        {@link AccessibilityFloatingMenu} when keyguard appears.
+     * @param mode accessibility button mode {@link AccessibilityButtonMode}
+     * @param targets accessibility button list; it should comes from
+     *                {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
      */
-    @Override
-    public void onAccessibilityStateChanged(boolean enabled) {
-        if (enabled) {
-            handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
+    private void handleFloatingMenuVisibility(boolean keyguardVisible,
+            @AccessibilityButtonMode int mode, String targets) {
+        if (keyguardVisible) {
+            destroyFloatingMenu();
+            return;
         }
 
-        mAccessibilityManager.removeAccessibilityStateChangeListener(this);
-    }
-
-    private void handleFloatingMenuVisibility(@AccessibilityButtonMode int mode, String targets) {
         if (shouldShowFloatingMenu(mode, targets)) {
             showFloatingMenu();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 146f430..f8e4bdc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -693,7 +693,6 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-
         // UdfpsController is not BiometricPrompt-specific. It can be active for keyguard or
         // enrollment.
         if (mUdfpsController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
index 7fc67bc..a52296a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
@@ -26,9 +26,11 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.view.Surface;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -45,20 +47,19 @@
 @SysUISingleton
 public class SidefpsController {
     private static final String TAG = "SidefpsController";
-    // TODO (b/188690214): define and retrieve values from framework via SensorProps
-    static final int DISPLAY_HEIGHT = 1804;
-    static final int DISPLAY_WIDTH = 2208;
-    static final int SFPS_INDICATOR_HEIGHT = 225;
-    static final int SFPS_Y = 500;
-    static final int SFPS_INDICATOR_WIDTH = 50;
-
-    @Nullable private SidefpsView mView;
-    private final FingerprintManager mFingerprintManager;
-    private final Context mContext;
+    @NonNull private final Context mContext;
     @NonNull private final LayoutInflater mInflater;
+    private final FingerprintManager mFingerprintManager;
     private final WindowManager mWindowManager;
     private final DelayableExecutor mFgExecutor;
+    // TODO: update mDisplayHeight and mDisplayWidth for multi-display devices
+    private final int mDisplayHeight;
+    private final int mDisplayWidth;
+
     private boolean mIsVisible = false;
+    @Nullable private SidefpsView mView;
+
+    static final int SFPS_AFFORDANCE_WIDTH = 50; // in default portrait mode
 
     @NonNull
     private final ISidefpsController mSidefpsControllerImpl = new ISidefpsController.Stub() {
@@ -110,6 +111,11 @@
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+        mDisplayHeight = displayMetrics.heightPixels;
+        mDisplayWidth = displayMetrics.widthPixels;
+
         mFingerprintManager.setSidefpsController(mSidefpsControllerImpl);
     }
 
@@ -122,7 +128,6 @@
 
     void hide() {
         if (mView != null) {
-            Log.v(TAG, "hideUdfpsOverlay | removing window");
             mWindowManager.removeView(mView);
             mView.setOnTouchListener(null);
             mView.setOnHoverListener(null);
@@ -132,13 +137,14 @@
         }
     }
 
+
     void onConfigurationChanged() {
-        // If overlay was hidden, it should remain hidden
-        if (!mIsVisible) {
+        // If mView is null or if view is hidden, then return.
+        if (mView == null || !mIsVisible) {
             return;
         }
-        // If the overlay needs to be shown, destroy the current overlay, and re-create and show
-        // the overlay with the updated LayoutParams.
+        // If the overlay needs to be displayed with a new configuration, destroy the current
+        // overlay, and re-create and show the overlay with the updated LayoutParams.
         hide();
         show();
     }
@@ -148,6 +154,25 @@
         for (FingerprintSensorPropertiesInternal props :
                 mFingerprintManager.getSensorPropertiesInternal()) {
             if (props.isAnySidefpsType()) {
+                // TODO(b/188690214): L155-L173 can be removed once sensorLocationX,
+                //  sensorLocationY, and sensorRadius are defined in sensorProps by the HAL
+                int sensorLocationX = 25;
+                int sensorLocationY = 610;
+                int sensorRadius = 112;
+
+                FingerprintSensorPropertiesInternal tempProps =
+                        new FingerprintSensorPropertiesInternal(
+                                props.sensorId,
+                                props.sensorStrength,
+                                props.maxEnrollmentsPerUser,
+                                props.componentInfo,
+                                props.sensorType,
+                                props.resetLockoutRequiresHardwareAuthToken,
+                                sensorLocationX,
+                                sensorLocationY,
+                                sensorRadius
+                        );
+                props = tempProps;
                 return props;
             }
         }
@@ -166,17 +191,30 @@
      */
     private WindowManager.LayoutParams computeLayoutParams() {
         mCoreLayoutParams.flags = getCoreLayoutParamFlags();
+        // Y value of top of affordance in portrait mode, X value of left of affordance in landscape
+        int sfpsLocationY = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
+        int sfpsAffordanceHeight = mSensorProps.sensorRadius * 2;
 
-        // Default dimensions assume portrait mode.
-        mCoreLayoutParams.x = DISPLAY_WIDTH - SFPS_INDICATOR_WIDTH;
-        mCoreLayoutParams.y = SFPS_Y;
-        mCoreLayoutParams.height = SFPS_INDICATOR_HEIGHT;
-        mCoreLayoutParams.width = SFPS_INDICATOR_WIDTH;
-
-        /*
-        TODO (b/188692405): recalculate coordinates for non-portrait configurations and folding
-         states
-        */
+        // Calculate coordinates of drawable area for the fps affordance, accounting for orientation
+        switch (mContext.getDisplay().getRotation()) {
+            case Surface.ROTATION_90:
+                mCoreLayoutParams.x = sfpsLocationY;
+                mCoreLayoutParams.y = 0;
+                mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
+                mCoreLayoutParams.width = sfpsAffordanceHeight;
+                break;
+            case Surface.ROTATION_270:
+                mCoreLayoutParams.x = mDisplayHeight - sfpsLocationY - sfpsAffordanceHeight;
+                mCoreLayoutParams.y = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
+                mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
+                mCoreLayoutParams.width = sfpsAffordanceHeight;
+                break;
+            default: // Portrait
+                mCoreLayoutParams.x = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
+                mCoreLayoutParams.y = sfpsLocationY;
+                mCoreLayoutParams.height = sfpsAffordanceHeight;
+                mCoreLayoutParams.width = SFPS_AFFORDANCE_WIDTH;
+        }
         return mCoreLayoutParams;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java
index b9e9b59..4fc59d6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.biometrics;
 
-import static com.android.systemui.biometrics.SidefpsController.SFPS_INDICATOR_HEIGHT;
-import static com.android.systemui.biometrics.SidefpsController.SFPS_INDICATOR_WIDTH;
+import static com.android.systemui.biometrics.SidefpsController.SFPS_AFFORDANCE_WIDTH;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -27,6 +26,7 @@
 import android.graphics.RectF;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.util.AttributeSet;
+import android.view.Surface;
 import android.widget.FrameLayout;
 
 /**
@@ -34,18 +34,22 @@
  */
 public class SidefpsView extends FrameLayout {
     private static final String TAG = "SidefpsView";
+    private static final int POINTER_SIZE_PX = 50;
+    private static final int ROUND_RADIUS = 15;
+
     @NonNull private final RectF mSensorRect;
     @NonNull private final Paint mSensorRectPaint;
     @NonNull private final Paint mPointerText;
-    private static final int POINTER_SIZE_PX = 50;
+    @NonNull private final Context mContext;
 
     // Used to obtain the sensor location.
     @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
+    @Surface.Rotation private int mOrientation;
 
     public SidefpsView(Context context, AttributeSet attrs) {
         super(context, attrs);
         super.setWillNotDraw(false);
-
+        mContext = context;
         mPointerText = new Paint(0 /* flags */);
         mPointerText.setAntiAlias(true);
         mPointerText.setColor(Color.WHITE);
@@ -54,7 +58,7 @@
         mSensorRect = new RectF();
         mSensorRectPaint = new Paint(0 /* flags */);
         mSensorRectPaint.setAntiAlias(true);
-        mSensorRectPaint.setColor(Color.BLUE);
+        mSensorRectPaint.setColor(Color.BLUE); // TODO: Fix Color
         mSensorRectPaint.setStyle(Paint.Style.FILL);
     }
 
@@ -65,11 +69,19 @@
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        canvas.drawRect(mSensorRect, mSensorRectPaint);
+        canvas.drawRoundRect(mSensorRect, ROUND_RADIUS, ROUND_RADIUS, mSensorRectPaint);
+        int x, y;
+        if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
+            x = mSensorProps.sensorRadius + 10;
+            y = SFPS_AFFORDANCE_WIDTH / 2 + 15;
+        } else {
+            x = SFPS_AFFORDANCE_WIDTH / 2 - 10;
+            y = mSensorProps.sensorRadius + 30;
+        }
         canvas.drawText(
                 ">",
-                SFPS_INDICATOR_WIDTH / 2 - 10, // TODO(b/188690214): retrieve from sensorProps
-                SFPS_INDICATOR_HEIGHT / 2 + 30, //TODO(b/188690214): retrieve from sensorProps
+                x,
+                y,
                 mPointerText
         );
     }
@@ -77,11 +89,19 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
+        mOrientation = mContext.getDisplay().getRotation();
+        if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
+            right = mSensorProps.sensorRadius * 2;
+            bottom = SFPS_AFFORDANCE_WIDTH;
+        } else {
+            right = SFPS_AFFORDANCE_WIDTH;
+            bottom = mSensorProps.sensorRadius * 2;
+        }
 
         mSensorRect.set(
                 0,
                 0,
-                SFPS_INDICATOR_WIDTH, //TODO(b/188690214): retrieve from sensorProps
-                SFPS_INDICATOR_HEIGHT); //TODO(b/188690214): retrieve from sensorProps
+                right,
+                bottom);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 187caf9..746621d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -41,6 +41,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.Prefs;
@@ -263,9 +264,10 @@
     @SysUISingleton
     public AccessibilityFloatingMenuController provideAccessibilityFloatingMenuController(
             Context context, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
-            AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
+            AccessibilityButtonModeObserver accessibilityButtonModeObserver,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         return new AccessibilityFloatingMenuController(context, accessibilityButtonTargetsObserver,
-                accessibilityButtonModeObserver);
+                accessibilityButtonModeObserver, keyguardUpdateMonitor);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index c04201c..0fdc4d8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
+import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
 import com.android.wm.shell.transition.ShellTransitions;
 
 import java.util.Optional;
@@ -94,6 +95,9 @@
         @BindsInstance
         Builder setStartingSurface(Optional<StartingSurface> s);
 
+        @BindsInstance
+        Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
+
         SysUIComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index bbd95b4..442d351 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -33,6 +33,7 @@
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
+import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
 import com.android.wm.shell.transition.ShellTransitions;
 
 import java.util.Optional;
@@ -102,4 +103,7 @@
 
     @WMSingleton
     Optional<StartingSurface> getStartingSurface();
+
+    @WMSingleton
+    Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 5dd2f06..8fee925 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -24,7 +24,6 @@
 import android.graphics.drawable.Icon;
 import android.media.MediaMetadata;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
@@ -77,7 +76,6 @@
     private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
     private final boolean mAboveStatusbar;
     private final NotificationEntryManager mNotificationEntryManager;
-    private final MediaRouter2Manager mRouterManager;
     @VisibleForTesting
     final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
 
@@ -94,8 +92,7 @@
     public MediaOutputController(@NonNull Context context, String packageName,
             boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
             lbm, ShadeController shadeController, ActivityStarter starter,
-            NotificationEntryManager notificationEntryManager, UiEventLogger uiEventLogger,
-            MediaRouter2Manager routerManager) {
+            NotificationEntryManager notificationEntryManager, UiEventLogger uiEventLogger) {
         mContext = context;
         mPackageName = packageName;
         mMediaSessionManager = mediaSessionManager;
@@ -107,7 +104,6 @@
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mUiEventLogger = uiEventLogger;
-        mRouterManager = routerManager;
     }
 
     void start(@NonNull Callback cb) {
@@ -138,9 +134,6 @@
         mLocalMediaManager.stopScan();
         mLocalMediaManager.registerCallback(this);
         mLocalMediaManager.startScan();
-        if (mRouterManager != null) {
-            mRouterManager.startScan();
-        }
     }
 
     void stop() {
@@ -151,9 +144,6 @@
             mLocalMediaManager.unregisterCallback(this);
             mLocalMediaManager.stopScan();
         }
-        if (mRouterManager != null) {
-            mRouterManager.stopScan();
-        }
         mMediaDevices.clear();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index e1a504c..0f340a5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.media.session.MediaSessionManager
-import android.media.MediaRouter2Manager
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.plugins.ActivityStarter
@@ -36,8 +35,7 @@
     private val shadeController: ShadeController,
     private val starter: ActivityStarter,
     private val notificationEntryManager: NotificationEntryManager,
-    private val uiEventLogger: UiEventLogger,
-    private val routerManager: MediaRouter2Manager
+    private val uiEventLogger: UiEventLogger
 ) {
     companion object {
         var mediaOutputDialog: MediaOutputDialog? = null
@@ -48,7 +46,7 @@
         mediaOutputDialog?.dismiss()
         mediaOutputDialog = MediaOutputController(context, packageName, aboveStatusBar,
                 mediaSessionManager, lbm, shadeController, starter, notificationEntryManager,
-                uiEventLogger, routerManager).run {
+                uiEventLogger).run {
             MediaOutputDialog(context, aboveStatusBar, this, uiEventLogger)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 7dccf01..ef7fac3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -53,8 +53,8 @@
     private val logger: OngoingCallLogger
 ) : CallbackController<OngoingCallListener> {
 
-    /** Null if there's no ongoing call. */
-    private var ongoingCallInfo: OngoingCallInfo? = null
+    /** Non-null if there's an active call notification. */
+    private var callNotificationInfo: CallNotificationInfo? = null
     /** True if the application managing the call is visible to the user. */
     private var isCallAppVisible: Boolean = true
     private var chipView: View? = null
@@ -76,19 +76,34 @@
         }
 
         override fun onEntryUpdated(entry: NotificationEntry) {
-            if (isOngoingCallNotification(entry)) {
-                ongoingCallInfo = OngoingCallInfo(
-                    entry.sbn.notification.`when`,
-                    entry.sbn.notification.contentIntent.intent,
-                    entry.sbn.uid)
-                updateChip()
-            } else if (isCallNotification(entry)) {
-                removeChip()
+            // We have a new call notification or our existing call notification has been updated.
+            // TODO(b/183229367): This likely won't work if you take a call from one app then
+            //  switch to a call from another app.
+            if (callNotificationInfo == null && isCallNotification(entry) ||
+                    (entry.sbn.key == callNotificationInfo?.key)) {
+                val newOngoingCallInfo = CallNotificationInfo(
+                        entry.sbn.key,
+                        entry.sbn.notification.`when`,
+                        entry.sbn.notification.contentIntent.intent,
+                        entry.sbn.uid,
+                        entry.sbn.notification.extras.getInt(
+                                Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
+                )
+                if (newOngoingCallInfo == callNotificationInfo) {
+                    return
+                }
+
+                callNotificationInfo = newOngoingCallInfo
+                if (newOngoingCallInfo.isOngoing) {
+                    updateChip()
+                } else {
+                    removeChip()
+                }
             }
         }
 
         override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-            if (isOngoingCallNotification(entry)) {
+            if (entry.sbn.key == callNotificationInfo?.key) {
                 removeChip()
             }
         }
@@ -126,7 +141,7 @@
      * Returns true if there's an active ongoing call that should be displayed in a status bar chip.
      */
     fun hasOngoingCall(): Boolean {
-        return ongoingCallInfo != null &&
+        return callNotificationInfo?.isOngoing == true &&
                 // When the user is in the phone app, don't show the chip.
                 !isCallAppVisible
     }
@@ -146,7 +161,7 @@
     }
 
     private fun updateChip() {
-        val currentOngoingCallInfo = ongoingCallInfo ?: return
+        val currentCallNotificationInfo = callNotificationInfo ?: return
 
         val currentChipView = chipView
         val timeView =
@@ -155,7 +170,7 @@
             currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
 
         if (currentChipView != null && timeView != null && backgroundView != null) {
-            timeView.base = currentOngoingCallInfo.callStartTime -
+            timeView.base = currentCallNotificationInfo.callStartTime -
                     System.currentTimeMillis() +
                     systemClock.elapsedRealtime()
             timeView.start()
@@ -163,17 +178,17 @@
             currentChipView.setOnClickListener {
                 logger.logChipClicked()
                 activityStarter.postStartActivityDismissingKeyguard(
-                        currentOngoingCallInfo.intent, 0,
+                        currentCallNotificationInfo.intent, 0,
                         ActivityLaunchAnimator.Controller.fromView(backgroundView))
             }
 
-            setUpUidObserver(currentOngoingCallInfo)
+            setUpUidObserver(currentCallNotificationInfo)
 
             mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
         } else {
-            // If we failed to update the chip, don't store the ongoing call info. Then
-            // [hasOngoingCall] will return false and we fall back to typical notification handling.
-            ongoingCallInfo = null
+            // If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
+            // will return false and we fall back to typical notification handling.
+            callNotificationInfo = null
 
             if (DEBUG) {
                 Log.w(TAG, "Ongoing call chip view could not be found; " +
@@ -185,14 +200,14 @@
     /**
      * Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
      */
-    private fun setUpUidObserver(currentOngoingCallInfo: OngoingCallInfo) {
+    private fun setUpUidObserver(currentCallNotificationInfo: CallNotificationInfo) {
         isCallAppVisible = isProcessVisibleToUser(
-                iActivityManager.getUidProcessState(currentOngoingCallInfo.uid, null))
+                iActivityManager.getUidProcessState(currentCallNotificationInfo.uid, null))
 
         uidObserver = object : IUidObserver.Stub() {
             override fun onUidStateChanged(
                     uid: Int, procState: Int, procStateSeq: Long, capability: Int) {
-                if (uid == currentOngoingCallInfo.uid) {
+                if (uid == currentCallNotificationInfo.uid) {
                     val oldIsCallAppVisible = isCallAppVisible
                     isCallAppVisible = isProcessVisibleToUser(procState)
                     if (oldIsCallAppVisible != isCallAppVisible) {
@@ -225,26 +240,23 @@
     }
 
     private fun removeChip() {
-        ongoingCallInfo = null
+        callNotificationInfo = null
         mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
         if (uidObserver != null) {
             iActivityManager.unregisterUidObserver(uidObserver)
         }
     }
 
-    private class OngoingCallInfo(
+    private data class CallNotificationInfo(
+        val key: String,
         val callStartTime: Long,
         val intent: Intent,
-        val uid: Int
+        val uid: Int,
+        /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
+        val isOngoing: Boolean
     )
 }
 
-private fun isOngoingCallNotification(entry: NotificationEntry): Boolean {
-    val extras = entry.sbn.notification.extras
-    return isCallNotification(entry) &&
-            extras.getInt(Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
-}
-
 private fun isCallNotification(entry: NotificationEntry): Boolean {
     return entry.sbn.notification.isStyle(Notification.CallStyle::class.java)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 4be4b11..c23e0b4 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -237,7 +237,7 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())
+            if (Intent.ACTION_USER_STARTED.equals(intent.getAction())
                     || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) {
                 if (!mDeviceProvisionedController.isCurrentUserSetup()) {
                     Log.i(TAG, "User setup not finished when " + intent.getAction()
@@ -282,7 +282,7 @@
     public void start() {
         if (DEBUG) Log.d(TAG, "Start");
         final IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_STARTED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 6c30674..6ef7450 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -73,6 +73,8 @@
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.startingsurface.StartingWindowController;
 import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
+import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
+import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -253,6 +255,24 @@
     }
 
     //
+    // Task to Surface communication
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<TaskSurfaceHelper> provideTaskSurfaceHelper(
+            Optional<TaskSurfaceHelperController> taskSurfaceController) {
+        return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
+    }
+
+    @WMSingleton
+    @Provides
+    static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
+            ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
+    }
+
+    //
     // Pip (optional feature)
     //
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
index 01b7ade..7aa4763 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
@@ -55,17 +56,18 @@
 
     @Before
     public void setUp() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
         mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext);
     }
 
     @Test
     public void onChange_haveListener_invokeCallback() {
         mAccessibilityButtonModeObserver.addListener(mListener);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
+                UserHandle.USER_CURRENT);
 
         mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
 
@@ -76,8 +78,9 @@
     public void onChange_noListener_noInvokeCallback() {
         mAccessibilityButtonModeObserver.addListener(mListener);
         mAccessibilityButtonModeObserver.removeListener(mListener);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
+                UserHandle.USER_CURRENT);
 
         mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
 
@@ -86,8 +89,9 @@
 
     @Test
     public void getCurrentAccessibilityButtonMode_expectedValue() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
+                UserHandle.USER_CURRENT);
 
         final int actualValue =
                 mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
index 1e49fc9..4145437 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
@@ -60,8 +61,9 @@
     @Test
     public void onChange_haveListener_invokeCallback() {
         mAccessibilityButtonTargetsObserver.addListener(mListener);
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
+                UserHandle.USER_CURRENT);
 
         mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
 
@@ -72,8 +74,9 @@
     public void onChange_listenerRemoved_noInvokeCallback() {
         mAccessibilityButtonTargetsObserver.addListener(mListener);
         mAccessibilityButtonTargetsObserver.removeListener(mListener);
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
+                UserHandle.USER_CURRENT);
 
         mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
 
@@ -82,8 +85,9 @@
 
     @Test
     public void getCurrentAccessibilityButtonTargets_expectedValue() {
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
+                UserHandle.USER_CURRENT);
 
         final String actualValue =
                 mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 49604ff..485df21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -59,6 +59,7 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.MathUtils;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.View;
@@ -252,6 +253,18 @@
     }
 
     @Test
+    public void onWindowBoundsChanged_buttonIsShowing_draggableBoundsChanged() {
+        mWindowManager.setWindowBounds(new Rect(0, 0, 800, 1000));
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
+
+        mWindowManager.setWindowBounds(new Rect(0, 0, 1000, 800));
+        mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED);
+
+        assertNotEquals(oldDraggableBounds, mMagnificationModeSwitch.mDraggableWindowBounds);
+    }
+
+    @Test
     public void onDraggingGestureFinish_buttonIsShowing_stickToRightEdge() {
         final int windowHalfWidth =
                 mWindowManager.getCurrentWindowMetrics().getBounds().width() / 2;
@@ -473,6 +486,28 @@
         assertEquals(newA11yWindowTitle, layoutParams.accessibilityTitle);
     }
 
+    @Test
+    public void onRotationChanged_buttonIsShowing_expectedYPosition() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int oldWindowHeight = windowBounds.height();
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        final float windowHeightFraction =
+                (float) mWindowManager.getLayoutParamsFromAttachedView().y / oldWindowHeight;
+
+        // The window bounds are changed due to the rotation change.
+        final Rect newWindowBounds = new Rect(0, 0, windowBounds.height(), windowBounds.width());
+        mWindowManager.setWindowBounds(newWindowBounds);
+        mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+
+        int expectedY = (int) (newWindowBounds.height() * windowHeightFraction);
+        expectedY = MathUtils.constrain(expectedY,
+                mMagnificationModeSwitch.mDraggableWindowBounds.top,
+                mMagnificationModeSwitch.mDraggableWindowBounds.bottom);
+        assertEquals(
+                "The Y position does not keep the same height ratio after the rotation changed.",
+                expectedY, mWindowManager.getLayoutParamsFromAttachedView().y);
+    }
+
     private void assertModeUnchanged(int expectedMode) {
         final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
index 5b1c441..41fd2b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
@@ -55,8 +56,8 @@
 
     @Test
     public void checkValue() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1, UserHandle.USER_CURRENT);
 
         assertThat(mTestObserver.getSettingsValue()).isEqualTo("1");
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
index 4f095ac..9621bed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
@@ -19,6 +19,7 @@
 import static android.view.WindowInsets.Type.systemGestures;
 
 import android.graphics.Insets;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.Display;
 import android.view.View;
@@ -31,6 +32,7 @@
 
     private final WindowManager mWindowManager;
     private View mView;
+    private Rect mWindowBounds = null;
     private WindowInsets mWindowInsets = null;
 
     TestableWindowManager(WindowManager windowManager) {
@@ -82,7 +84,8 @@
                 .setInsets(systemGestures(), systemGesturesInsets)
                 .build();
         final WindowMetrics windowMetrics = new WindowMetrics(
-                mWindowManager.getCurrentWindowMetrics().getBounds(),
+                mWindowBounds == null ? mWindowManager.getCurrentWindowMetrics().getBounds()
+                        : mWindowBounds,
                 mWindowInsets == null ? insets : mWindowInsets);
         return windowMetrics;
     }
@@ -103,6 +106,10 @@
         return (WindowManager.LayoutParams) mView.getLayoutParams();
     }
 
+    public void setWindowBounds(Rect bounds) {
+        mWindowBounds = bounds;
+    }
+
     public void setWindowInsets(WindowInsets insets) {
         mWindowInsets = insets;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index a83f038..5b50e89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -24,15 +24,16 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -41,7 +42,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -56,11 +58,13 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private AccessibilityFloatingMenuController mController;
     private AccessibilityButtonTargetsObserver mTargetsObserver;
     private AccessibilityButtonModeObserver mModeObserver;
-    @Mock
-    private AccessibilityManager mMockA11yManager;
+    @Captor
+    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
+    private KeyguardUpdateMonitorCallback mKeyguardCallback;
 
     @Test
     public void initController_registerListeners() {
@@ -70,43 +74,107 @@
                 any(AccessibilityButtonTargetsObserver.TargetsChangedListener.class));
         verify(mModeObserver).addListener(
                 any(AccessibilityButtonModeObserver.ModeChangedListener.class));
-        verify(mMockA11yManager).addAccessibilityStateChangeListener(any(
-                AccessibilityManager.AccessibilityStateChangeListener.class));
+        verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
     }
 
     @Test
-    public void initController_accessibilityManagerEnabled_showWidget() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
-        when(mMockA11yManager.isEnabled()).thenReturn(true);
-
+    public void onUserUnlocked_keyguardNotShow_showWidget() {
+        enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
+        captureKeyguardUpdateMonitorCallback();
+        mKeyguardCallback.onKeyguardVisibilityChanged(false);
+
+        mKeyguardCallback.onUserUnlocked();
 
         assertThat(mController.mFloatingMenu).isNotNull();
-        verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController);
     }
 
     @Test
-    public void initController_accessibilityManagerDisabledThenCallbackToEnabled_showWidget() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
-        when(mMockA11yManager.isEnabled()).thenReturn(false);
-
+    public void onUserUnlocked_keyguardShowing_destroyWidget() {
+        enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
-        mController.onAccessibilityStateChanged(true);
+        captureKeyguardUpdateMonitorCallback();
+        mKeyguardCallback.onKeyguardVisibilityChanged(true);
+
+        mKeyguardCallback.onUserUnlocked();
+
+        assertThat(mController.mFloatingMenu).isNull();
+    }
+
+    @Test
+    public void onKeyguardVisibilityChanged_showing_destroyWidget() {
+        enableAccessibilityFloatingMenuConfig();
+        mController = setUpController();
+        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContext);
+        captureKeyguardUpdateMonitorCallback();
+        mKeyguardCallback.onUserUnlocked();
+
+        mKeyguardCallback.onKeyguardVisibilityChanged(true);
+
+        assertThat(mController.mFloatingMenu).isNull();
+    }
+
+    @Test
+    public void onKeyguardVisibilityChanged_notShow_showWidget() {
+        enableAccessibilityFloatingMenuConfig();
+        mController = setUpController();
+        captureKeyguardUpdateMonitorCallback();
+        mKeyguardCallback.onUserUnlocked();
+
+        mKeyguardCallback.onKeyguardVisibilityChanged(false);
 
         assertThat(mController.mFloatingMenu).isNotNull();
-        verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController);
+    }
+
+    @Test
+    public void onUserSwitching_destroyWidget() {
+        final int fakeUserId = 1;
+        enableAccessibilityFloatingMenuConfig();
+        mController = setUpController();
+        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContext);
+        captureKeyguardUpdateMonitorCallback();
+
+        mKeyguardCallback.onUserSwitching(fakeUserId);
+
+        assertThat(mController.mFloatingMenu).isNull();
+    }
+
+    @Test
+    public void onUserSwitch_onKeyguardVisibilityChangedToTrue_destroyWidget() {
+        final int fakeUserId = 1;
+        enableAccessibilityFloatingMenuConfig();
+        mController = setUpController();
+        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContext);
+        captureKeyguardUpdateMonitorCallback();
+        mKeyguardCallback.onUserUnlocked();
+        mKeyguardCallback.onKeyguardVisibilityChanged(true);
+
+        mKeyguardCallback.onUserSwitching(fakeUserId);
+        mKeyguardCallback.onUserSwitchComplete(fakeUserId);
+
+        assertThat(mController.mFloatingMenu).isNull();
+    }
+
+    @Test
+    public void onUserSwitch_onKeyguardVisibilityChangedToFalse_showWidget() {
+        final int fakeUserId = 1;
+        enableAccessibilityFloatingMenuConfig();
+        mController = setUpController();
+        captureKeyguardUpdateMonitorCallback();
+        mKeyguardCallback.onUserUnlocked();
+        mKeyguardCallback.onKeyguardVisibilityChanged(false);
+
+        mKeyguardCallback.onUserSwitching(fakeUserId);
+        mKeyguardCallback.onUserSwitchComplete(fakeUserId);
+
+        assertThat(mController.mFloatingMenu).isNotNull();
     }
 
     @Test
     public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
+                UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -116,8 +184,8 @@
 
     @Test
     public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "");
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -126,9 +194,10 @@
     }
 
     @Test
-    public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_showWidget() {
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+    public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
+                UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -138,8 +207,8 @@
 
     @Test
     public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "");
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -149,8 +218,9 @@
 
     @Test
     public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
+                UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -160,8 +230,9 @@
 
     @Test
     public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
+                UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged("");
@@ -170,10 +241,10 @@
     }
 
     @Test
-    public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_showWidget() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
+    public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -182,10 +253,10 @@
     }
 
     @Test
-    public void onAccessibilityButtonTargetsChanged_buttonModeAndNoButtonTargets_destroyWidget() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
+    public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged("");
@@ -196,9 +267,23 @@
     private AccessibilityFloatingMenuController setUpController() {
         mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class));
         mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class));
-        mContext.addMockSystemService(AccessibilityManager.class, mMockA11yManager);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         return new AccessibilityFloatingMenuController(mContext, mTargetsObserver,
-                mModeObserver);
+                mModeObserver, mKeyguardUpdateMonitor);
+    }
+
+    private void enableAccessibilityFloatingMenuConfig() {
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
+                UserHandle.USER_CURRENT);
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
+                UserHandle.USER_CURRENT);
+    }
+
+    private void captureKeyguardUpdateMonitorCallback() {
+        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+        mKeyguardCallback = mKeyguardCallbackCaptor.getValue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index c00b394..589ae2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.media.MediaRouter2Manager;
 import android.media.session.MediaSessionManager;
 import android.os.Bundle;
 import android.testing.AndroidTestingRunner;
@@ -50,7 +49,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class MediaOutputBaseDialogTest extends SysuiTestCase {
 
     private static final String TEST_PACKAGE = "test_package";
@@ -64,7 +63,6 @@
     private NotificationEntryManager mNotificationEntryManager =
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
-    private final MediaRouter2Manager mRouterManager = mock(MediaRouter2Manager.class);
 
     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
     private MediaOutputController mMediaOutputController;
@@ -77,7 +75,7 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mRouterManager);
+                mNotificationEntryManager, mUiEventLogger);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index c1a3994..d1a617b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -32,7 +32,6 @@
 import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
-import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
@@ -92,7 +91,6 @@
     private NotificationEntryManager mNotificationEntryManager =
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
-    private final MediaRouter2Manager mRouter2Manager = mock(MediaRouter2Manager.class);
 
     private Context mSpyContext;
     private MediaOutputController mMediaOutputController;
@@ -115,7 +113,7 @@
 
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mRouter2Manager);
+                mNotificationEntryManager, mUiEventLogger);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -159,7 +157,7 @@
     public void start_withoutPackageName_verifyMediaControllerInit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mRouter2Manager);
+                mNotificationEntryManager, mUiEventLogger);
 
         mMediaOutputController.start(mCb);
 
@@ -180,7 +178,7 @@
     public void stop_withoutPackageName_verifyMediaControllerDeinit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mRouter2Manager);
+                mNotificationEntryManager, mUiEventLogger);
 
         mMediaOutputController.start(mCb);
 
@@ -451,7 +449,7 @@
     public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mRouter2Manager);
+                mNotificationEntryManager, mUiEventLogger);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index e47a5e7..86f6bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.when;
 
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.session.MediaSessionManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -66,7 +65,6 @@
     private final NotificationEntryManager mNotificationEntryManager =
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
-    private final MediaRouter2Manager mRouterManager = mock(MediaRouter2Manager.class);
 
     private MediaOutputDialog mMediaOutputDialog;
     private MediaOutputController mMediaOutputController;
@@ -76,7 +74,7 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mRouterManager);
+                mNotificationEntryManager, mUiEventLogger);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = new MediaOutputDialog(mContext, false,
                 mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index 6111099..c296ff5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -21,7 +21,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.media.MediaRouter2Manager;
 import android.media.session.MediaSessionManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -65,7 +64,6 @@
     private NotificationEntryManager mNotificationEntryManager =
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
-    private final MediaRouter2Manager mRouterManager = mock(MediaRouter2Manager.class);
 
     private MediaOutputGroupDialog mMediaOutputGroupDialog;
     private MediaOutputController mMediaOutputController;
@@ -75,7 +73,7 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mRouterManager);
+                mNotificationEntryManager, mUiEventLogger);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
                 mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 075d1dd..e55361e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -587,7 +587,7 @@
         ));
 
         // Back scrim should be visible after start dragging
-        mScrimController.setPanelExpansion(0.5f);
+        mScrimController.setPanelExpansion(0.3f);
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
                 mNotificationsScrim, SEMI_TRANSPARENT,
@@ -1049,7 +1049,7 @@
     @Test
     public void testScrimsVisible_whenShadeVisible() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
-        mScrimController.setPanelExpansion(0.5f);
+        mScrimController.setPanelExpansion(0.3f);
         // notifications scrim alpha change require calling setQsPosition
         mScrimController.setQsPosition(0, 300);
         finishAnimationsImmediately();
@@ -1064,7 +1064,7 @@
     public void testScrimsVisible_whenShadeVisible_clippingQs() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
-        mScrimController.setPanelExpansion(0.5f);
+        mScrimController.setPanelExpansion(0.3f);
         // notifications scrim alpha change require calling setQsPosition
         mScrimController.setQsPosition(0.5f, 300);
         finishAnimationsImmediately();
@@ -1114,7 +1114,7 @@
         mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
 
         assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.8f, /* expansion */ 0.8f);
-        assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.2f, /* expansion */ 0.2f);
+        assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.47f, /* expansion */ 0.2f);
     }
 
     @Test
@@ -1122,7 +1122,7 @@
         mScrimController.transitionTo(ScrimState.KEYGUARD);
 
         assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.2f, /* expansion */ 0.4f);
-        assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.8f, /* expansion */ 0.2f);
+        assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.52f, /* expansion */ 0.2f);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 3a71ecf..ca3cff9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -55,6 +55,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.reset
+
 import org.mockito.MockitoAnnotations
 
 private const val CALL_UID = 900
@@ -138,15 +140,51 @@
                 .onOngoingCallStateChanged(anyBoolean())
     }
 
+    /**
+     * If a call notification is never added before #onEntryRemoved is called, then the listener
+     * should never be notified.
+     */
     @Test
-    fun onEntryRemoved_ongoingCallNotif_listenerNotified() {
+    fun onEntryRemoved_callNotifNeverAddedBeforehand_listenerNotNotified() {
         notifCollectionListener.onEntryRemoved(createOngoingCallNotifEntry(), REASON_USER_STOPPED)
 
+        verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
+    }
+
+    @Test
+    fun onEntryRemoved_callNotifAddedThenRemoved_listenerNotified() {
+        val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+        notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+        reset(mockOngoingCallListener)
+
+        notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
         verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
+    /** Regression test for b/188491504. */
     @Test
-    fun onEntryRemoved_notOngoingCallNotif_listenerNotNotified() {
+    fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
+        val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+        notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+        reset(mockOngoingCallListener)
+
+        // Create another notification based on the ongoing call one, but remove the features that
+        // made it a call notification.
+        val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry)
+        removedEntryBuilder.modifyNotification(context).style = null
+
+        notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
+
+        verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+    }
+
+
+    @Test
+    fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() {
+        notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
+        reset(mockOngoingCallListener)
+
         notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
 
         verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
@@ -158,6 +196,20 @@
     }
 
     @Test
+    fun hasOngoingCall_unrelatedNotifSent_returnsFalse() {
+        notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
+
+        assertThat(controller.hasOngoingCall()).isFalse()
+    }
+
+    @Test
+    fun hasOngoingCall_screeningCallNotifSent_returnsFalse() {
+        notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
+
+        assertThat(controller.hasOngoingCall()).isFalse()
+    }
+
+    @Test
     fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() {
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
                 .thenReturn(PROC_STATE_INVISIBLE)
@@ -224,6 +276,7 @@
     fun setChipView_whenHasOngoingCallIsTrue_listenerNotifiedWithNewView() {
         // Start an ongoing call.
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+        reset(mockOngoingCallListener)
 
         lateinit var newChipView: View
         TestableLooper.get(this).runWithLooper {
@@ -233,10 +286,7 @@
         // Change the chip view associated with the controller.
         controller.setChipView(newChipView)
 
-        // Verify the listener was notified once for the initial call and again when the new view
-        // was set.
-        verify(mockOngoingCallListener, times(2))
-                .onOngoingCallStateChanged(anyBoolean())
+        verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
@@ -245,6 +295,7 @@
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
                 .thenReturn(PROC_STATE_INVISIBLE)
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+        reset(mockOngoingCallListener)
 
         val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
         verify(mockIActivityManager).registerUidObserver(
@@ -256,9 +307,7 @@
         mainExecutor.advanceClockToLast()
         mainExecutor.runAllReady();
 
-        // Once for when the call was started, and another time when the process visibility changes.
-        verify(mockOngoingCallListener, times(2))
-                .onOngoingCallStateChanged(anyBoolean())
+        verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
@@ -267,6 +316,7 @@
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
                 .thenReturn(PROC_STATE_VISIBLE)
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+        reset(mockOngoingCallListener)
 
         val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
         verify(mockIActivityManager).registerUidObserver(
@@ -278,9 +328,7 @@
         mainExecutor.advanceClockToLast()
         mainExecutor.runAllReady();
 
-        // Once for when the call was started, and another time when the process visibility changes.
-        verify(mockOngoingCallListener, times(2))
-                .onOngoingCallStateChanged(anyBoolean())
+        verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index cfaffd0..3d2a0f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -360,6 +360,13 @@
     }
 
     @Test
+    public void onUserSwitch_setsTheme() {
+        mBroadcastReceiver.getValue().onReceive(null,
+                new Intent(Intent.ACTION_USER_STARTED));
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void onProfileAdded_setsTheme() {
         mBroadcastReceiver.getValue().onReceive(null,
                 new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 6644b28..0f9a6fa 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -45,6 +45,7 @@
 import com.android.internal.os.ZygoteConnectionConstants;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.am.TraceErrorLogger;
 import com.android.server.wm.SurfaceAnimationThread;
 
 import java.io.BufferedReader;
@@ -59,6 +60,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 /** This class calls its monitor every minute. Killing this process if they don't return **/
@@ -159,6 +161,8 @@
     private boolean mAllowRestart = true;
     private final List<Integer> mInterestingJavaPids = new ArrayList<>();
 
+    private final TraceErrorLogger mTraceErrorLogger;
+
     /**
      * Used for checking status of handle threads and scheduling monitor callbacks.
      */
@@ -367,6 +371,8 @@
         // See the notes on DEFAULT_TIMEOUT.
         assert DB ||
                 DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
+
+        mTraceErrorLogger = new TraceErrorLogger();
     }
 
     /**
@@ -667,6 +673,19 @@
             // Then kill this process so that the system will restart.
             EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
 
+            final UUID errorId;
+            if (mTraceErrorLogger.isAddErrorIdEnabled()) {
+                errorId = mTraceErrorLogger.generateErrorId();
+                mTraceErrorLogger.addErrorIdToTrace(errorId);
+            } else {
+                errorId = null;
+            }
+
+            // Log the atom as early as possible since it is used as a mechanism to trigger
+            // Perfetto. Ideally, the Perfetto trace capture should happen as close to the
+            // point in time when the Watchdog happens as possible.
+            FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
+
             long anrTime = SystemClock.uptimeMillis();
             StringBuilder report = new StringBuilder();
             report.append(MemoryPressureUtil.currentPsiState());
@@ -691,7 +710,6 @@
             // Try to add the error to the dropbox, but assuming that the ActivityManager
             // itself may be deadlocked.  (which has happened, causing this statement to
             // deadlock and the watchdog as a whole to be ineffective)
-            final String localSubject = subject;
             Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
                     public void run() {
                         // If a watched thread hangs before init() is called, we don't have a
@@ -699,11 +717,9 @@
                         if (mActivity != null) {
                             mActivity.addErrorToDropBox(
                                     "watchdog", null, "system_server", null, null, null,
-                                    null, report.toString(), stack, null, null, null, null);
-
+                                    null, report.toString(), stack, null, null, null,
+                                    errorId);
                         }
-                        FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED,
-                                localSubject);
                     }
                 };
             dropboxThread.start();
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8041ec4..9e42900 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -811,7 +811,8 @@
             }
             mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                     AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
-                    true, false, null, false);
+                    true, false, null, false, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                    AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
         }
 
         final ServiceMap smap = getServiceMapLocked(r.userId);
@@ -1881,7 +1882,8 @@
                         mAm.mAppOpsService.startOperation(
                                 AppOpsManager.getToken(mAm.mAppOpsService),
                                 AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName,
-                                null, true, false, "", false);
+                                null, true, false, "", false, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                                AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
                         registerAppOpCallbackLocked(r);
                         mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
                     }
@@ -6191,7 +6193,8 @@
                 r.mFgsNotificationDeferred,
                 r.mFgsNotificationShown,
                 durationMs,
-                r.mStartForegroundCount);
+                r.mStartForegroundCount,
+                ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName));
     }
 
     boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e500e4..2947621 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -156,6 +156,7 @@
 import android.app.AnrController;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
 import android.app.ApplicationErrorReport;
 import android.app.ApplicationExitInfo;
@@ -346,6 +347,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.NonaFunction;
@@ -353,6 +355,7 @@
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintFunction;
 import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.DisplayThread;
@@ -1833,7 +1836,9 @@
         final int[] cameraOp = {AppOpsManager.OP_CAMERA};
         mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() {
             @Override
-            public void opActiveChanged(int op, int uid, String packageName, boolean active) {
+            public void opActiveChanged(int op, int uid, String packageName, String attributionTag,
+                    boolean active, @AttributionFlags int attributionFlags,
+                    int attributionChainId) {
                 cameraActiveChanged(uid, active);
             }
         });
@@ -6216,8 +6221,20 @@
                 // signature we just use the first package in the UID. For shared
                 // UIDs we may blame the wrong app but that is Okay as they are
                 // in the same security/privacy sandbox.
-                final AndroidPackage androidPackage = mPackageManagerInt
-                        .getPackage(Binder.getCallingUid());
+                final int uid = Binder.getCallingUid();
+                // Here we handle some of the special UIDs (mediaserver, systemserver, etc)
+                final String packageName = AppOpsManager.resolvePackageName(uid,
+                        /*packageName*/ null);
+                final AndroidPackage androidPackage;
+                if (packageName != null) {
+                    androidPackage = mPackageManagerInt.getPackage(packageName);
+                } else {
+                    androidPackage = mPackageManagerInt.getPackage(uid);
+                }
+                if (androidPackage == null) {
+                    Log.e(TAG, "Cannot find package for uid: " + uid);
+                    return null;
+                }
                 final AttributionSource attributionSource = new AttributionSource(
                         Binder.getCallingUid(), androidPackage.getPackageName(), null);
                 pfd = cph.provider.openFile(attributionSource, uri, "r", null);
@@ -16866,7 +16883,7 @@
                 try {
                     return superImpl.apply(code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getNext()),
+                            attributionSource.getToken(), attributionSource.getNext()),
                             shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                             skiProxyOperation);
                 } finally {
@@ -16882,8 +16899,9 @@
                 @Nullable String packageName, @Nullable String attributionTag,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
-                @NonNull NonaFunction<IBinder, Integer, Integer, String, String, Boolean,
-                        Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+                @AttributionFlags int attributionFlags, int attributionChainId,
+                @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
+                        Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) {
             if (uid == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
@@ -16891,57 +16909,62 @@
                 try {
                     return superImpl.apply(token, code, shellUid, "com.android.shell",
                             attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage);
+                            shouldCollectMessage, attributionFlags, attributionChainId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
             return superImpl.apply(token, code, uid, packageName, attributionTag,
-                    startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                    startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                    attributionFlags, attributionChainId);
         }
 
         @Override
-        public SyncNotedAppOp startProxyOperation(IBinder token, int code,
+        public SyncNotedAppOp startProxyOperation(int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-                boolean skipProsyOperation, @NonNull OctFunction<IBinder, Integer,
-                        AttributionSource, Boolean, Boolean, String, Boolean, Boolean,
-                        SyncNotedAppOp> superImpl) {
+                boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+                @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+                @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
+                        Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
             if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(token, code, new AttributionSource(shellUid,
-                                    "com.android.shell", attributionSource.getAttributionTag(),
-                                    attributionSource.getNext()), startIfModeDefault,
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                            skipProsyOperation);
+                    return superImpl.apply(code, new AttributionSource(shellUid,
+                            "com.android.shell", attributionSource.getAttributionTag(),
+                            attributionSource.getToken(), attributionSource.getNext()),
+                            startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                            shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                            proxiedAttributionFlags, attributionChainId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(token, code, attributionSource, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProsyOperation);
+            return superImpl.apply(code, attributionSource, startIfModeDefault,
+                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
+                    proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
         @Override
-        public void finishProxyOperation(IBinder clientId, int code,
-                @NonNull AttributionSource attributionSource,
-                @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) {
+        public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+                boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
+                        Boolean, Void> superImpl) {
             if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    superImpl.apply(clientId, code, new AttributionSource(shellUid,
+                    superImpl.apply(code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getNext()));
+                            attributionSource.getToken(), attributionSource.getNext()),
+                            skipProxyOperation);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            superImpl.apply(clientId, code, attributionSource);
+            superImpl.apply(code, attributionSource, skipProxyOperation);
         }
 
         private boolean isTargetOp(int code) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index dd24148..5506ad1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -121,4 +121,12 @@
 
         return (((double) hash) / Integer.MAX_VALUE) <= rate;
     }
+
+    /**
+     * @param shortInstanceName {@link ServiceRecord#shortInstanceName}.
+     * @return hash of the ServiceRecord's shortInstanceName, combined with ANDROID_ID.
+     */
+    public static int hashComponentNameForAtom(String shortInstanceName) {
+        return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 41edd75..508bd06 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -272,6 +272,13 @@
                 errorId = null;
             }
 
+            // This atom is only logged with the purpose of triggering Perfetto and the logging
+            // needs to happen as close as possible to the time when the ANR is detected.
+            // Also, it needs to be logged after adding the error id to the trace, to make sure
+            // the error id is present in the trace when the Perfetto trace is captured.
+            FrameworkStatsLog.write(FrameworkStatsLog.ANR_OCCURRED_PROCESSING_STARTED,
+                    mApp.processName);
+
             // Dump thread traces as quickly as we can, starting with "interesting" processes.
             firstPids.add(pid);
 
diff --git a/services/core/java/com/android/server/am/TraceErrorLogger.java b/services/core/java/com/android/server/am/TraceErrorLogger.java
index f055be2..55f5715 100644
--- a/services/core/java/com/android/server/am/TraceErrorLogger.java
+++ b/services/core/java/com/android/server/am/TraceErrorLogger.java
@@ -16,8 +16,8 @@
 
 package com.android.server.am;
 
+import android.os.Build;
 import android.os.Trace;
-import android.provider.DeviceConfig;
 
 import java.util.UUID;
 
@@ -26,15 +26,12 @@
  *
  * @hide
  */
-class TraceErrorLogger {
+public class TraceErrorLogger {
     private static final String COUNTER_PREFIX = "ErrorId:";
-    private static final String ADD_ERROR_ID = "add_error_id";
     private static final int PLACEHOLDER_VALUE = 1;
 
     public boolean isAddErrorIdEnabled() {
-        return DeviceConfig
-                .getBoolean(DeviceConfig.NAMESPACE_TRACE_ERROR_LOGGER, ADD_ERROR_ID,
-                        false);
+        return Build.IS_DEBUGGABLE;
     }
 
     /**
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f70b0fb..96db1ad 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -267,7 +267,8 @@
                     mAllowDownscale = true;
                 }
             } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "Failed to get package metadata", e);
+                // Not all packages are installed, hence ignore those that are not installed yet.
+                Slog.v(TAG, "Failed to get package metadata");
             }
             final String configString = DeviceConfig.getProperty(
                     DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName);
@@ -531,9 +532,8 @@
             final ApplicationInfo applicationInfo = mPackageManager
                     .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
             if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
-                Slog.e(TAG, "Ignoring attempt to get the Game Mode for '" + packageName
-                        + "' which is not categorized as a game: applicationInfo.flags = "
-                        + applicationInfo.flags + ", category = " + applicationInfo.category);
+                // The game mode for applications that are not identified as game is always
+                // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)}
                 return GameManager.GAME_MODE_UNSUPPORTED;
             }
         } catch (PackageManager.NameNotFoundException e) {
@@ -571,9 +571,8 @@
             final ApplicationInfo applicationInfo = mPackageManager
                     .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
             if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
-                Slog.e(TAG, "Ignoring attempt to set the Game Mode for '" + packageName
-                        + "' which is not categorized as a game: applicationInfo.flags = "
-                        + applicationInfo.flags + ", category = " + applicationInfo.category);
+                // Ignore attempt to set the game mode for applications that are not identified
+                // as game. See {@link PackageManager#setApplicationCategoryHint(String, int)}
                 return;
             }
         } catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 541dcdc..fdb3d8c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -84,6 +84,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
 import android.app.AppOpsManager.AttributedOpEntry;
 import android.app.AppOpsManager.HistoricalOps;
 import android.app.AppOpsManager.Mode;
@@ -200,6 +201,7 @@
 import java.util.Objects;
 import java.util.Scanner;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 public class AppOpsService extends IAppOpsService.Stub {
@@ -257,11 +259,6 @@
     private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
     private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
-    //TODO: remove this when development is done.
-    private static final int DEBUG_FGS_ALLOW_WHILE_IN_USE = 0;
-    private static final int DEBUG_FGS_ENFORCE_TYPE = 1;
-
-
     final Context mContext;
     final AtomicFile mFile;
     private final @Nullable File mNoteOpCallerStacktracesFile;
@@ -414,9 +411,10 @@
         }
 
         InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
-                @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName,
-                @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
-                @OpFlags int flags) throws RemoteException {
+                @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
+                @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+                @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
+                int attributionFlags, int attributionChainId) throws RemoteException {
 
             InProgressStartOpEvent recycled = acquire();
 
@@ -427,13 +425,14 @@
             }
 
             if (recycled != null) {
-                recycled.reinit(startTime, elapsedTime, clientId, onDeath, uidState, flags,
-                        proxyInfo, mOpEventProxyInfoPool);
+                recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
+                        uidState, flags, proxyInfo,  attributionFlags, attributionChainId,
+                        mOpEventProxyInfoPool);
                 return recycled;
             }
 
-            return new InProgressStartOpEvent(startTime, elapsedTime, clientId, onDeath, uidState,
-                    proxyInfo, flags);
+            return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
+                    onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
         }
     }
 
@@ -685,6 +684,9 @@
         /** Id of the client that started the event */
         private @NonNull IBinder mClientId;
 
+        /** The attribution tag for this operation */
+        private @Nullable String mAttributionTag;
+
         /** To call when client dies */
         private @NonNull Runnable mOnDeath;
 
@@ -700,30 +702,44 @@
         /** How many times the op was started but not finished yet */
         int numUnfinishedStarts;
 
+        /** The attribution flags related to this event */
+        private @AttributionFlags int mAttributionFlags;
+
+        /** The id of the attribution chain this even is a part of */
+        private int mAttributionChainId;
+
         /**
          * Create a new {@link InProgressStartOpEvent}.
          *
          * @param startTime The time {@link #startOperation} was called
          * @param startElapsedTime The elapsed time when {@link #startOperation} was called
          * @param clientId The client id of the caller of {@link #startOperation}
+         * @param attributionTag The attribution tag for the operation.
          * @param onDeath The code to execute on client death
          * @param uidState The uidstate of the app {@link #startOperation} was called for
+         * @param attributionFlags the attribution flags for this operation.
+         * @param attributionChainId the unique id of the attribution chain this op is a part of.
          * @param proxy The proxy information, if {@link #startProxyOperation} was called
          * @param flags The trusted/nontrusted/self flags.
          *
          * @throws RemoteException If the client is dying
          */
         private InProgressStartOpEvent(long startTime, long startElapsedTime,
-                @NonNull IBinder clientId, @NonNull Runnable onDeath,
-                @AppOpsManager.UidState int uidState, @Nullable OpEventProxyInfo proxy,
-                @OpFlags int flags) throws RemoteException {
+                @NonNull IBinder clientId, @Nullable String attributionTag,
+                @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
+                @Nullable OpEventProxyInfo proxy, @OpFlags int flags,
+                @AttributionFlags int attributionFlags, int attributionChainId)
+                throws RemoteException {
             mStartTime = startTime;
             mStartElapsedTime = startElapsedTime;
             mClientId = clientId;
+            mAttributionTag = attributionTag;
             mOnDeath = onDeath;
             mUidState = uidState;
             mProxy = proxy;
             mFlags = flags;
+            mAttributionFlags = attributionFlags;
+            mAttributionChainId = attributionChainId;
 
             clientId.linkToDeath(this, 0);
         }
@@ -744,21 +760,27 @@
          * @param startTime The time {@link #startOperation} was called
          * @param startElapsedTime The elapsed time when {@link #startOperation} was called
          * @param clientId The client id of the caller of {@link #startOperation}
+         * @param attributionTag The attribution tag for this operation.
          * @param onDeath The code to execute on client death
          * @param uidState The uidstate of the app {@link #startOperation} was called for
          * @param flags The flags relating to the proxy
          * @param proxy The proxy information, if {@link #startProxyOperation} was called
+         * @param attributionFlags the attribution flags for this operation.
+         * @param attributionChainId the unique id of the attribution chain this op is a part of.
          * @param proxyPool The pool to release previous {@link OpEventProxyInfo} to
          *
          * @throws RemoteException If the client is dying
          */
         public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
-                @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, @OpFlags int flags,
-                @Nullable OpEventProxyInfo proxy, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool
+                @Nullable String attributionTag, @NonNull Runnable onDeath,
+                @AppOpsManager.UidState int uidState, @OpFlags int flags,
+                @Nullable OpEventProxyInfo proxy, @AttributionFlags int attributionFlags,
+                int attributionChainId, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool
         ) throws RemoteException {
             mStartTime = startTime;
             mStartElapsedTime = startElapsedTime;
             mClientId = clientId;
+            mAttributionTag = attributionTag;
             mOnDeath = onDeath;
             mUidState = uidState;
             mFlags = flags;
@@ -767,6 +789,8 @@
                 proxyPool.release(mProxy);
             }
             mProxy = proxy;
+            mAttributionFlags = attributionFlags;
+            mAttributionChainId = attributionChainId;
 
             clientId.linkToDeath(this, 0);
         }
@@ -791,7 +815,7 @@
             return mUidState;
         }
 
-        /** @return proxy info for the access */
+        /** @return proxy tag for the access */
         public @Nullable OpEventProxyInfo getProxy() {
             return mProxy;
         }
@@ -800,6 +824,16 @@
         public @OpFlags int getFlags() {
             return mFlags;
         }
+
+        /** @return attributoin flags used for the access */
+        public @AttributionFlags int getAttributionFlags() {
+            return mAttributionFlags;
+        }
+
+        /** @return attributoin chiang id for the access */
+        public int getAttributionChainId() {
+            return mAttributionChainId;
+        }
     }
 
     private final class AttributedOp {
@@ -943,32 +977,38 @@
          * @param proxyAttributionTag The attribution tag of the proxy app
          * @param uidState UID state of the app startOp is called for
          * @param flags The proxy flags
+         * @param attributionFlags The attribution flags associated with this operation.
+         * @param attributionChainId The if of the attribution chain this operations is a part of.
          */
         public void started(@NonNull IBinder clientId, int proxyUid,
                 @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException {
-            started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
-                    true);
+                @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
+                int attributionFlags, int attributionChainId) throws RemoteException {
+            started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+                    uidState, flags,/*triggerCallbackIfNeeded*/ true, attributionFlags,
+                    attributionChainId);
         }
 
         private void started(@NonNull IBinder clientId, int proxyUid,
                 @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
                 @AppOpsManager.UidState int uidState, @OpFlags int flags,
-                boolean triggerCallbackIfNeeded) throws RemoteException {
-            startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState, flags, triggerCallbackIfNeeded, true);
+                boolean triggerCallbackIfNeeded, @AttributionFlags int attributionFlags,
+                int attributionChainId) throws RemoteException {
+            startedOrPaused(clientId, proxyUid, proxyPackageName,
+                    proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
+                    /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
         }
 
         private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
                 @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
                 @AppOpsManager.UidState int uidState, @OpFlags int flags,
-                boolean triggerCallbackIfNeeded, boolean isStarted) throws RemoteException {
+                boolean triggerCallbackIfNeeded, boolean isStarted, @AttributionFlags
+                int attributionFlags, int attributionChainId) throws RemoteException {
             if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
-                scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                        parent.packageName, true);
+                scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
+                        tag, true, attributionFlags, attributionChainId);
             }
 
-
             if (isStarted && mInProgressEvents == null) {
                 mInProgressEvents = new ArrayMap<>(1);
             } else if (mPausedInProgressEvents == null) {
@@ -981,9 +1021,10 @@
             InProgressStartOpEvent event = events.get(clientId);
             if (event == null) {
                 event = mInProgressStartOpEventPool.acquire(startTime,
-                        SystemClock.elapsedRealtime(), clientId,
+                        SystemClock.elapsedRealtime(), clientId, tag,
                         PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
-                        proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags);
+                        proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+                        attributionFlags, attributionChainId);
                 events.put(clientId, event);
             } else {
                 if (uidState != event.mUidState) {
@@ -994,10 +1035,10 @@
             event.numUnfinishedStarts++;
 
             if (isStarted) {
+                // TODO: Consider storing the attribution chain flags and id
                 mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                         parent.packageName, tag, uidState, flags, startTime);
             }
-
         }
 
         /**
@@ -1063,7 +1104,8 @@
                         // TODO ntmyren: Also callback for single attribution tag activity changes
                         if (triggerCallbackIfNeeded && !parent.isRunning()) {
                             scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                                    parent.packageName, false);
+                                    parent.packageName, tag, false, event.getAttributionFlags(),
+                                    event.getAttributionChainId());
                         }
                     }
                 }
@@ -1103,9 +1145,10 @@
          */
         public void createPaused(@NonNull IBinder clientId, int proxyUid,
                 @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-                @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException {
+                @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
+                int attributionFlags, int attributionChainId) throws RemoteException {
             startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState, flags, true, false);
+                    uidState, flags, true, false, attributionFlags, attributionChainId);
         }
 
         /**
@@ -1124,9 +1167,11 @@
                 InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
                 mPausedInProgressEvents.put(event.mClientId, event);
                 finishOrPause(event.mClientId, true, true);
+
+                scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+                        parent.packageName, tag, false,
+                        event.getAttributionFlags(), event.getAttributionChainId());
             }
-            scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                    parent.packageName, false);
             mInProgressEvents = null;
         }
 
@@ -1153,10 +1198,10 @@
                 event.mStartTime = startTime;
                 mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                         parent.packageName, tag, event.mUidState, event.mFlags, startTime);
-            }
-            if (shouldSendActive) {
-                scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                        parent.packageName, true);
+                if (shouldSendActive) {
+                    scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
+                            tag, true, event.getAttributionFlags(), event.getAttributionChainId());
+                }
             }
             mPausedInProgressEvents = null;
         }
@@ -1210,10 +1255,12 @@
                         // previously removed unfinished start counts back
                         if (proxy != null) {
                             started(event.getClientId(), proxy.getUid(), proxy.getPackageName(),
-                                    proxy.getAttributionTag(), newState, event.getFlags(), false);
+                                    proxy.getAttributionTag(), newState, event.getFlags(), false,
+                                    event.getAttributionFlags(), event.getAttributionChainId());
                         } else {
                             started(event.getClientId(), Process.INVALID_UID, null, null, newState,
-                                    OP_FLAG_SELF, false);
+                                    OP_FLAG_SELF, false, event.getAttributionFlags(),
+                                    event.getAttributionChainId());
                         }
 
                         InProgressStartOpEvent newEvent = mInProgressEvents.get(binders.get(i));
@@ -3321,9 +3368,10 @@
                 scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                         AppOpsManager.MODE_IGNORED);
                 if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName);
+                        + " package " + packageName + "flags: " +
+                        AppOpsManager.flagsToString(flags));
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
-                        packageName);
+                        packageName + " flags: " + AppOpsManager.flagsToString(flags));
             }
             final Op op = getOpLocked(ops, code, uid, true);
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
@@ -3349,7 +3397,7 @@
                 if (uidMode != AppOpsManager.MODE_ALLOWED) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName);
+                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
                     attributedOp.rejected(uidState.state, flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                             uidMode);
@@ -3362,7 +3410,7 @@
                 if (mode != AppOpsManager.MODE_ALLOWED) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName);
+                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
                     attributedOp.rejected(uidState.state, flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                             mode);
@@ -3373,7 +3421,8 @@
                 Slog.d(TAG,
                         "noteOperation: allowing code " + code + " uid " + uid + " package "
                                 + packageName + (attributionTag == null ? ""
-                                : "." + attributionTag));
+                                : "." + attributionTag) + " flags: "
+                                + AppOpsManager.flagsToString(flags));
             }
             scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                     AppOpsManager.MODE_ALLOWED);
@@ -3674,16 +3723,18 @@
     public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
             @Nullable String packageName, @Nullable String attributionTag,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
-            String message, boolean shouldCollectMessage) {
+            String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId) {
         return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName,
                 attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                shouldCollectMessage, attributionFlags, attributionChainId);
     }
 
     private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code, int uid,
             @Nullable String packageName, @Nullable String attributionTag,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
-            boolean shouldCollectMessage) {
+            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId) {
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
         verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
@@ -3707,29 +3758,36 @@
         }
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
                 Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage, false);
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
+                attributionChainId, /*dryRun*/ false);
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+    public SyncNotedAppOp startProxyOperation(int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-            boolean skipProxyOperation) {
-        return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
+            boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+            @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
+        return mCheckOpsDelegateDispatcher.startProxyOperation(code, attributionSource,
                 startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                skipProxyOperation);
+                skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
+                attributionChainId);
     }
 
-    private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
+    private SyncNotedAppOp startProxyOperationImpl(int code,
             @NonNull AttributionSource attributionSource,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-            boolean shouldCollectMessage, boolean skipProxyOperation) {
+            boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
+            int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
+            int attributionChainId) {
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
+        final IBinder proxyToken = attributionSource.getToken();
         final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+        final IBinder proxiedToken = attributionSource.getNextToken();
 
         verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
@@ -3762,10 +3820,11 @@
 
         if (!skipProxyOperation) {
             // Test if the proxied operation will succeed before starting the proxy operation
-            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code, proxiedUid,
-                    resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(proxiedToken, code,
+                    proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, true);
+                    shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                    proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
             if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
                 return testProxiedOp;
             }
@@ -3773,19 +3832,21 @@
             final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
                     : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
 
-            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
+            final SyncNotedAppOp proxyAppOp = startOperationUnchecked(proxyToken, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
                     proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
-                    shouldCollectMessage, false);
+                    shouldCollectMessage, proxyAttributionFlags, attributionChainId,
+                    /*dryRun*/ false);
             if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
                 return proxyAppOp;
             }
         }
 
-        return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+        return startOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
                 proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
                 proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, false);
+                shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
+                /*dryRun*/ false);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3796,7 +3857,8 @@
             @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
             String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage, boolean dryRun) {
+            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId, boolean dryRun) {
         RestrictionBypass bypass;
         try {
             bypass = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3818,7 +3880,8 @@
                             flags, AppOpsManager.MODE_IGNORED);
                 }
                 if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
-                        + " package " + packageName);
+                        + " package " + packageName + " flags: "
+                        + AppOpsManager.flagsToString(flags));
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                         packageName);
             }
@@ -3835,7 +3898,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
                                 + switchCode + " (" + code + ") uid " + uid + " package "
-                                + packageName);
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
                     }
                     if (!dryRun) {
                         attributedOp.rejected(uidState.state, flags);
@@ -3852,7 +3915,7 @@
                         && (!startIfModeDefault || mode != MODE_DEFAULT)) {
                     if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
-                            + packageName);
+                            + packageName + " flags: " + AppOpsManager.flagsToString(flags));
                     if (!dryRun) {
                         attributedOp.rejected(uidState.state, flags);
                         scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
@@ -3862,15 +3925,18 @@
                 }
             }
             if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
-                    + " package " + packageName + " restricted: " + isRestricted);
+                    + " package " + packageName + " restricted: " + isRestricted
+                    + " flags: " + AppOpsManager.flagsToString(flags));
             if (!dryRun) {
                 try {
                     if (isRestricted) {
                         attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.state, flags);
+                                proxyAttributionTag, uidState.state, flags, attributionFlags,
+                                attributionChainId);
                     } else {
                         attributedOp.started(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.state, flags);
+                                proxyAttributionTag, uidState.state, flags, attributionFlags,
+                                attributionChainId);
                     }
                 } catch (RemoteException e) {
                     throw new RuntimeException(e);
@@ -3905,21 +3971,26 @@
     }
 
     @Override
-    public void finishProxyOperation(IBinder clientId, int code,
-            @NonNull AttributionSource attributionSource) {
-        mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource);
+    public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+            boolean skipProxyOperation) {
+        mCheckOpsDelegateDispatcher.finishProxyOperation(code, attributionSource,
+                skipProxyOperation);
     }
 
-    private Void finishProxyOperationImpl(IBinder clientId, int code,
-            @NonNull AttributionSource attributionSource) {
+    private Void finishProxyOperationImpl(int code, @NonNull AttributionSource attributionSource,
+            boolean skipProxyOperation) {
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
+        final IBinder proxyToken = attributionSource.getToken();
         final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+        final IBinder proxiedToken = attributionSource.getNextToken();
 
-        verifyIncomingUid(proxyUid);
+        skipProxyOperation = resolveSkipProxyOperation(skipProxyOperation, attributionSource);
+
+        verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
         verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
         verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
@@ -3930,8 +4001,10 @@
             return null;
         }
 
-        finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
-                proxyAttributionTag);
+        if (!skipProxyOperation) {
+            finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName,
+                    proxyAttributionTag);
+        }
 
         String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
                 proxiedPackageName);
@@ -3939,7 +4012,7 @@
             return null;
         }
 
-        finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+        finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
                 proxiedAttributionTag);
 
         return null;
@@ -3981,8 +4054,9 @@
         }
     }
 
-    private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, String packageName,
-            boolean active) {
+    private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
+            String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
+            int attributionFlags, int attributionChainId) {
         ArraySet<ActiveCallback> dispatchedCallbacks = null;
         final int callbackListCount = mActiveWatchers.size();
         for (int i = 0; i < callbackListCount; i++) {
@@ -4003,11 +4077,13 @@
         }
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 AppOpsService::notifyOpActiveChanged,
-                this, dispatchedCallbacks, code, uid, packageName, active));
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+                attributionFlags, attributionChainId));
     }
 
     private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
-            int code, int uid, String packageName, boolean active) {
+            int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+            boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
         // There are features watching for mode changes such as window manager
         // and location manager which are in our process. The callbacks in these
         // features may require permissions our remote caller does not have.
@@ -4017,7 +4093,8 @@
             for (int i = 0; i < callbackCount; i++) {
                 final ActiveCallback callback = callbacks.valueAt(i);
                 try {
-                    callback.mCallback.opActiveChanged(code, uid, packageName, active);
+                    callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+                            active, attributionFlags, attributionChainId);
                 } catch (RemoteException e) {
                     /* do nothing */
                 }
@@ -5023,6 +5100,8 @@
     }
 
     static class Shell extends ShellCommand {
+        static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
+
         final IAppOpsService mInterface;
         final AppOpsService mInternal;
 
@@ -5491,7 +5570,9 @@
                     if (shell.packageName != null) {
                         shell.mInterface.startOperation(shell.mToken, shell.op, shell.packageUid,
                                 shell.packageName, shell.attributionTag, true, true,
-                                "appops start shell command", true);
+                                "appops start shell command", true,
+                                AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR,
+                                shell.sAttributionChainIds.incrementAndGet());
                     } else {
                         return -1;
                     }
@@ -6421,7 +6502,9 @@
             @NonNull String proxiedPackageName) {
         Objects.requireNonNull(proxyPackageName);
         Objects.requireNonNull(proxiedPackageName);
-        Binder.withCleanCallingIdentity(() -> {
+        final long callingUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
             final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
                     proxiedPackageName, new int[] {op});
             if (packageOps == null || packageOps.isEmpty()) {
@@ -6436,13 +6519,13 @@
                 return false;
             }
             final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
-                    AppOpsManager.OP_FLAG_TRUSTED_PROXY
-                            | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY);
-            return proxyInfo != null && Binder.getCallingUid() == proxyInfo.getUid()
+                    OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+            return proxyInfo != null && callingUid == proxyInfo.getUid()
                     && proxyPackageName.equals(proxyInfo.getPackageName())
                     && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
-        });
-        return false;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -7275,89 +7358,101 @@
         public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
                 @Nullable String packageName, @NonNull String attributionTag,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
-                @Nullable String message, boolean shouldCollectMessage) {
+                @Nullable String message, boolean shouldCollectMessage,
+                @AttributionFlags int attributionFlags, int attributionChainId) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
                     return mPolicy.startOperation(token, code, uid, packageName,
                             attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, this::startDelegateOperationImpl);
+                            shouldCollectMessage, attributionFlags, attributionChainId,
+                            this::startDelegateOperationImpl);
                 } else {
                     return mPolicy.startOperation(token, code, uid, packageName, attributionTag,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, AppOpsService.this::startOperationImpl);
+                            shouldCollectMessage, attributionFlags, attributionChainId,
+                            AppOpsService.this::startOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
                 return startDelegateOperationImpl(token, code, uid, packageName, attributionTag,
                         startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                        shouldCollectMessage);
+                        shouldCollectMessage, attributionFlags, attributionChainId);
             }
             return startOperationImpl(token, code, uid, packageName, attributionTag,
-                    startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                    startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                    attributionFlags, attributionChainId);
         }
 
         private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code, int uid,
                 @Nullable String packageName, @Nullable String attributionTag,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-                boolean shouldCollectMessage) {
+                boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+                int attributionChainId) {
             return mCheckOpsDelegate.startOperation(token, code, uid, packageName, attributionTag,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    AppOpsService.this::startOperationImpl);
+                    attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
         }
 
-        public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+        public SyncNotedAppOp startProxyOperation(int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-                boolean skipProxyOperation) {
+                boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+                @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.startProxyOperation(clientId, code, attributionSource,
+                    return mPolicy.startProxyOperation(code, attributionSource,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, skipProxyOperation,
+                            shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                            proxiedAttributionFlags, attributionChainId,
                             this::startDelegateProxyOperationImpl);
                 } else {
-                    return mPolicy.startProxyOperation(clientId, code, attributionSource,
+                    return mPolicy.startProxyOperation(code, attributionSource,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, skipProxyOperation,
+                            shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                            proxiedAttributionFlags, attributionChainId,
                             AppOpsService.this::startProxyOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                return startDelegateProxyOperationImpl(clientId, code,
-                        attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                        shouldCollectMessage, skipProxyOperation);
+                return startDelegateProxyOperationImpl(code, attributionSource,
+                        startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                        shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                        proxiedAttributionFlags, attributionChainId);
             }
-            return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
+            return startProxyOperationImpl(code, attributionSource, startIfModeDefault,
+                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
+                    proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
         }
 
-
-        private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder token, int code,
+        private SyncNotedAppOp startDelegateProxyOperationImpl(int code,
                 @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
                 boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-                boolean skipProxyOperation) {
-            return mCheckOpsDelegate.startProxyOperation(token, code, attributionSource,
+                boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+                @AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) {
+            return mCheckOpsDelegate.startProxyOperation(code, attributionSource,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    skipProxyOperation, AppOpsService.this::startProxyOperationImpl);
+                    skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs,
+                    attributionChainId, AppOpsService.this::startProxyOperationImpl);
         }
 
-        public void finishProxyOperation(IBinder clientId, int code,
-                @NonNull AttributionSource attributionSource) {
+        public void finishProxyOperation(int code,
+                @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    mPolicy.finishProxyOperation(clientId, code, attributionSource,
-                            this::finishDelegateProxyOperationImpl);
+                    mPolicy.finishProxyOperation(code, attributionSource,
+                            skipProxyOperation, this::finishDelegateProxyOperationImpl);
                 } else {
-                    mPolicy.finishProxyOperation(clientId, code, attributionSource,
-                            AppOpsService.this::finishProxyOperationImpl);
+                    mPolicy.finishProxyOperation(code, attributionSource,
+                            skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                finishDelegateProxyOperationImpl(clientId, code, attributionSource);
+                finishDelegateProxyOperationImpl(code, attributionSource, skipProxyOperation);
+            } else {
+                finishProxyOperationImpl(code, attributionSource, skipProxyOperation);
             }
-            finishProxyOperationImpl(clientId, code, attributionSource);
         }
 
-        private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
-                @NonNull AttributionSource attributionSource) {
-            mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
+        private Void finishDelegateProxyOperationImpl(int code,
+                @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
+            mCheckOpsDelegate.finishProxyOperation(code, attributionSource, skipProxyOperation,
                     AppOpsService.this::finishProxyOperationImpl);
             return null;
         }
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index c1209d4..62db886 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -154,7 +154,7 @@
             // must have the required permission and the users must be in the same profile group
             // in order to launch any of its own activities.
             if (callerUserId != userId) {
-                final int permissionFlag =  PermissionChecker.checkPermissionForPreflight(
+                final int permissionFlag = PermissionChecker.checkPermissionForPreflight(
                         mContext,
                         INTERACT_ACROSS_PROFILES,
                         callingPid,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6a399bd..00a68a0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -341,7 +341,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
-import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.content.om.OverlayConfig;
@@ -897,20 +896,6 @@
      * Only non-null during an OTA, and even then it is nulled again once systemReady().
      */
     private @Nullable ArraySet<String> mExistingPackages = null;
-
-    /**
-     * List of code paths that need to be released when the system becomes ready.
-     * <p>
-     * NOTE: We have to delay releasing cblocks for no other reason than we cannot
-     * retrieve the setting {@link Secure#RELEASE_COMPRESS_BLOCKS_ON_INSTALL}. When
-     * we no longer need to read that setting, cblock release can occur in the
-     * constructor.
-     *
-     * @see Secure#RELEASE_COMPRESS_BLOCKS_ON_INSTALL
-     * @see #systemReady()
-     */
-    private @Nullable List<File> mReleaseOnSystemReady;
-
     /**
      * Whether or not system app permissions should be promoted from install to runtime.
      */
@@ -7922,21 +7907,6 @@
                 IoUtils.closeQuietly(handle);
             }
         }
-        if (ret == PackageManager.INSTALL_SUCCEEDED) {
-            // NOTE: During boot, we have to delay releasing cblocks for no other reason than
-            // we cannot retrieve the setting {@link Secure#RELEASE_COMPRESS_BLOCKS_ON_INSTALL}.
-            // When we no longer need to read that setting, cblock release can occur always
-            // occur here directly
-            if (!mSystemReady) {
-                if (mReleaseOnSystemReady == null) {
-                    mReleaseOnSystemReady = new ArrayList<>();
-                }
-                mReleaseOnSystemReady.add(dstCodePath);
-            } else {
-                final ContentResolver resolver = mContext.getContentResolver();
-                F2fsUtils.releaseCompressedBlocks(resolver, dstCodePath);
-            }
-        }
         if (ret != PackageManager.INSTALL_SUCCEEDED) {
             if (!dstCodePath.exists()) {
                 return null;
@@ -17807,10 +17777,6 @@
             if (mRet == PackageManager.INSTALL_SUCCEEDED) {
                 mRet = args.copyApk();
             }
-            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
-                F2fsUtils.releaseCompressedBlocks(
-                        mContext.getContentResolver(), new File(args.getCodePath()));
-            }
             if (mParentInstallParams != null) {
                 mParentInstallParams.tryProcessInstallRequest(args, mRet);
             } else {
@@ -17818,6 +17784,7 @@
                 processInstallRequestsAsync(
                         res.returnCode == PackageManager.INSTALL_SUCCEEDED,
                         Collections.singletonList(new InstallRequest(args, res)));
+
             }
         }
     }
@@ -24157,15 +24124,8 @@
     public void systemReady() {
         enforceSystemOrRoot("Only the system can claim the system is ready");
 
-        final ContentResolver resolver = mContext.getContentResolver();
-        if (mReleaseOnSystemReady != null) {
-            for (int i = mReleaseOnSystemReady.size() - 1; i >= 0; --i) {
-                final File dstCodePath = mReleaseOnSystemReady.get(i);
-                F2fsUtils.releaseCompressedBlocks(resolver, dstCodePath);
-            }
-            mReleaseOnSystemReady = null;
-        }
         mSystemReady = true;
+        final ContentResolver resolver = mContext.getContentResolver();
         ContentObserver co = new ContentObserver(mHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -24228,7 +24188,13 @@
         mPermissionManager.onSystemReady();
 
         int[] grantPermissionsUserIds = EMPTY_INT_ARRAY;
-        for (int userId : UserManagerService.getInstance().getUserIds()) {
+        final List<UserInfo> livingUsers = mInjector.getUserManagerInternal().getUsers(
+                /* excludePartial= */ true,
+                /* excludeDying= */ true,
+                /* excludePreCreated= */ false);
+        final int livingUserCount = livingUsers.size();
+        for (int i = 0; i < livingUserCount; i++) {
+            final int userId = livingUsers.get(i).id;
             if (mPmInternal.isPermissionUpgradeNeeded(userId)) {
                 grantPermissionsUserIds = ArrayUtils.appendInt(
                         grantPermissionsUserIds, userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cf18156..e8897ca 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -699,6 +699,8 @@
         mContext.registerReceiver(mConfigurationChangeReceiver,
                 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
                 null, mHandler);
+
+        markEphemeralUsersForRemoval();
     }
 
     /**
@@ -709,17 +711,33 @@
         return mLocalService;
     }
 
+    /** Marks all ephemeral users as slated for deletion. **/
+    private void markEphemeralUsersForRemoval() {
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserInfo ui = mUsers.valueAt(i).info;
+                if (ui.isEphemeral() && !ui.preCreated && ui.id != UserHandle.USER_SYSTEM) {
+                    addRemovingUserIdLocked(ui.id);
+                    ui.partial = true;
+                    ui.flags |= UserInfo.FLAG_DISABLED;
+                }
+            }
+        }
+    }
+
+    /* Prunes out any partially created or partially removed users. */
     void cleanupPartialUsers() {
-        // Prune out any partially created, partially removed and ephemeral users.
         ArrayList<UserInfo> partials = new ArrayList<>();
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i).info;
-                if ((ui.partial || ui.guestToRemove || (ui.isEphemeral() && !ui.preCreated))
-                        && i != 0) {
+                if ((ui.partial || ui.guestToRemove) && ui.id != UserHandle.USER_SYSTEM) {
                     partials.add(ui);
-                    addRemovingUserIdLocked(ui.id);
+                    if (!mRemovingUserIds.get(ui.id)) {
+                        addRemovingUserIdLocked(ui.id);
+                    }
                     ui.partial = true;
                 }
             }
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 1bfa72f..b0f8ee1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
@@ -65,6 +66,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.compat.annotation.ChangeId;
@@ -108,6 +110,7 @@
 import android.permission.IOnPermissionsChangeListener;
 import android.permission.IPermissionChecker;
 import android.permission.IPermissionManager;
+import android.permission.PermissionCheckerManager;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
 import android.permission.PermissionManagerInternal;
@@ -171,6 +174,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiFunction;
 
 /**
@@ -3320,8 +3324,8 @@
     }
 
     @Override
-    public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
-        return mAttributionSourceRegistry.registerAttributionSource(source);
+    public void registerAttributionSource(@NonNull AttributionSource source) {
+        mAttributionSourceRegistry.registerAttributionSource(source);
     }
 
     @Override
@@ -5295,8 +5299,8 @@
          * @param permissionName the name of the permission to be checked
          * @param userId the user ID
          * @param superImpl the original implementation that can be delegated to
-         * @return {@link android.content.pm.PackageManager.PERMISSION_GRANTED} if the package has
-         * the permission, or {@link android.content.pm.PackageManager.PERMISSION_DENITED} otherwise
+         * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
+         * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
          *
          * @see android.content.pm.PackageManager#checkPermission(String, String)
          */
@@ -5310,8 +5314,8 @@
          * @param uid the UID to be checked
          * @param permissionName the name of the permission to be checked
          * @param superImpl the original implementation that can be delegated to
-         * @return {@link android.content.pm.PackageManager.PERMISSION_GRANTED} if the package has
-         * the permission, or {@link android.content.pm.PackageManager.PERMISSION_DENITED} otherwise
+         * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
+         * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
          */
         int checkUidPermission(int uid, @NonNull String permissionName,
                 BiFunction<Integer, String, Integer> superImpl);
@@ -5395,8 +5399,7 @@
 
         private final WeakHashMap<IBinder, AttributionSource> mAttributions = new WeakHashMap<>();
 
-        public @NonNull AttributionSource registerAttributionSource(
-                @NonNull AttributionSource source) {
+        public void registerAttributionSource(@NonNull AttributionSource source) {
             //   Here we keep track of attribution sources that were created by an app
             // from an attribution chain that called into the app and the apps's
             // own attribution source. An app can register an attribution chain up
@@ -5443,10 +5446,7 @@
             }
 
             synchronized (mLock) {
-                final IBinder token = new Binder();
-                final AttributionSource result = source.withToken(token);
-                mAttributions.put(token, result);
-                return result;
+                mAttributions.put(source.getToken(), source);
             }
         }
 
@@ -5472,6 +5472,8 @@
         private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
                 = new ConcurrentHashMap<>();
 
+        private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
+
         private final @NonNull Context mContext;
         private final @NonNull AppOpsManager mAppOpsManager;
 
@@ -5481,53 +5483,108 @@
         }
 
         @Override
-        @PermissionChecker.PermissionResult
+        @PermissionCheckerManager.PermissionResult
         public int checkPermission(@NonNull String permission,
                 @NonNull AttributionSourceState attributionSourceState, @Nullable String message,
-                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
+                int attributedOp) {
             Objects.requireNonNull(permission);
             Objects.requireNonNull(attributionSourceState);
             final AttributionSource attributionSource = new AttributionSource(
                     attributionSourceState);
             final int result = checkPermission(mContext, permission, attributionSource, message,
-                    forDataDelivery, startDataDelivery, fromDatasource);
+                    forDataDelivery, startDataDelivery, fromDatasource, attributedOp);
             // Finish any started op if some step in the attribution chain failed.
             if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
-                finishDataDelivery(AppOpsManager.permissionToOp(permission),
-                        attributionSource.asState());
+                if (attributedOp == AppOpsManager.OP_NONE) {
+                    finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
+                            attributionSource.asState(), fromDatasource);
+                } else {
+                    finishDataDelivery(attributedOp, attributionSource.asState(), fromDatasource);
+                }
             }
             return result;
         }
 
         @Override
-        public void finishDataDelivery(@NonNull String op,
-                @NonNull AttributionSourceState attributionSourceState) {
-            if (op == null || attributionSourceState.packageName == null) {
+        public void finishDataDelivery(int op,
+                @NonNull AttributionSourceState attributionSourceState, boolean fromDatasource) {
+            Objects.requireNonNull(attributionSourceState);
+
+            if (op == AppOpsManager.OP_NONE) {
                 return;
             }
-            mAppOpsManager.finishProxyOp(op, new AttributionSource(attributionSourceState));
-            if (attributionSourceState.next != null) {
-                finishDataDelivery(op, attributionSourceState.next[0]);
+
+            AttributionSource current = new AttributionSource(attributionSourceState);
+            AttributionSource next = null;
+
+            while (true) {
+                final boolean skipCurrentFinish = (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.asState() == attributionSourceState)
+                        && next != null && !current.isTrusted(mContext)) {
+                    return;
+                }
+
+                // 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.asState() == attributionSourceState && next != null
+                        && next.getNext() == null);
+                final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+                final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+                        ? current : next;
+
+                if (selfAccess) {
+                    final String resolvedPackageName = resolvePackageName(mContext, accessorSource);
+                    if (resolvedPackageName == null) {
+                        return;
+                    }
+                    mAppOpsManager.finishOp(accessorSource.getToken(), op,
+                            accessorSource.getUid(), resolvedPackageName,
+                            accessorSource.getAttributionTag());
+                } else {
+                    final AttributionSource resolvedAttributionSource =
+                            resolveAttributionSource(mContext, accessorSource);
+                    if (resolvedAttributionSource.getPackageName() == null) {
+                        return;
+                    }
+                    mAppOpsManager.finishProxyOp(AppOpsManager.opToPublicName(op),
+                            resolvedAttributionSource, skipCurrentFinish);
+                }
+
+                if (next == null || next.getNext() == null) {
+                    return;
+                }
+
+                current = next;
             }
         }
 
         @Override
-        @PermissionChecker.PermissionResult
+        @PermissionCheckerManager.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);
+                finishDataDelivery(op, attributionSource, /*fromDatasource*/ false);
             }
             return  result;
         }
 
-        @PermissionChecker.PermissionResult
+        @PermissionCheckerManager.PermissionResult
         private static int checkPermission(@NonNull Context context, @NonNull String permission,
                 @NonNull AttributionSource attributionSource, @Nullable String message,
-                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
+                int attributedOp) {
             PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
 
             if (permissionInfo == null) {
@@ -5549,7 +5606,7 @@
             }
             if (permissionInfo.isRuntime()) {
                 return checkRuntimePermission(context, permission, attributionSource, message,
-                        forDataDelivery, startDataDelivery, fromDatasource);
+                        forDataDelivery, startDataDelivery, fromDatasource, attributedOp);
             }
 
             if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
@@ -5558,15 +5615,14 @@
             }
 
             if (attributionSource.getNext() != null) {
-                return checkPermission(context, permission,
-                        attributionSource.getNext(), message, forDataDelivery,
-                        startDataDelivery, /*fromDatasource*/ false);
+                return checkPermission(context, permission, attributionSource.getNext(), message,
+                        forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp);
             }
 
             return PermissionChecker.PERMISSION_GRANTED;
         }
 
-        @PermissionChecker.PermissionResult
+        @PermissionCheckerManager.PermissionResult
         private static int checkAppOpPermission(@NonNull Context context,
                 @NonNull String permission, @NonNull AttributionSource attributionSource,
                 @Nullable String message, boolean forDataDelivery, boolean fromDatasource) {
@@ -5586,7 +5642,7 @@
 
                 // 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)
+                if (!(fromDatasource && current.equals(attributionSource))
                         && next != null && !current.isTrusted(context)) {
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
@@ -5595,12 +5651,15 @@
                 // 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);
+                        && current.equals(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);
+                        selfAccess, singleReceiverFromDatasource, AppOpsManager.OP_NONE,
+                        AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                        AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
 
                 switch (opMode) {
                     case AppOpsManager.MODE_IGNORED:
@@ -5631,9 +5690,12 @@
         private static int checkRuntimePermission(@NonNull Context context,
                 @NonNull String permission, @NonNull AttributionSource attributionSource,
                 @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
-                boolean fromDatasource) {
+                boolean fromDatasource, int attributedOp) {
             // Now let's check the identity chain...
             final int op = AppOpsManager.permissionToOpCode(permission);
+            final int attributionChainId = (startDataDelivery)
+                    ? sAttributionChainIds.incrementAndGet()
+                    : AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
 
             AttributionSource current = attributionSource;
             AttributionSource next = null;
@@ -5644,7 +5706,7 @@
 
                 // 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)
+                if (!(fromDatasource && current.equals(attributionSource))
                         && next != null && !current.isTrusted(context)) {
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
@@ -5678,12 +5740,21 @@
                 // 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);
+                        && current.equals(attributionSource)
+                        && next != null && next.getNext() == null);
                 final boolean selfAccess = singleReceiverFromDatasource || next == null;
 
+                final int proxyAttributionFlags = (!skipCurrentChecks)
+                        ? resolveProxyAttributionFlags(attributionSource, current, fromDatasource,
+                                startDataDelivery, selfAccess)
+                        : AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+                final int proxiedAttributionFlags = resolveProxiedAttributionFlags(
+                        attributionSource, next, fromDatasource, startDataDelivery, selfAccess);
+
                 final int opMode = performOpTransaction(context, op, current, message,
                         forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                        singleReceiverFromDatasource);
+                        singleReceiverFromDatasource, attributedOp, proxyAttributionFlags,
+                        proxiedAttributionFlags, attributionChainId);
 
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
@@ -5714,6 +5785,50 @@
             return permissionGranted;
         }
 
+        private static @AttributionFlags int resolveProxyAttributionFlags(
+                @NonNull AttributionSource attributionChain,
+                @NonNull AttributionSource current, boolean fromDatasource,
+                boolean startDataDelivery, boolean selfAccess) {
+            return resolveAttributionFlags(attributionChain, current, fromDatasource,
+                    startDataDelivery, selfAccess, /*flagsForProxy*/ true);
+        }
+
+        private static @AttributionFlags int resolveProxiedAttributionFlags(
+                @NonNull AttributionSource attributionChain,
+                @NonNull AttributionSource current, boolean fromDatasource,
+                boolean startDataDelivery, boolean selfAccess) {
+            return resolveAttributionFlags(attributionChain, current, fromDatasource,
+                    startDataDelivery, selfAccess, /*flagsForProxy*/ false);
+        }
+
+        private static @AttributionFlags int resolveAttributionFlags(
+                @NonNull AttributionSource attributionChain,
+                @NonNull AttributionSource current, boolean fromDatasource,
+                boolean startDataDelivery, boolean selfAccess, boolean flagsForProxy) {
+            if (current == null || !startDataDelivery) {
+                return AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+            }
+            if (flagsForProxy) {
+                if (selfAccess) {
+                    return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+                } else if (!fromDatasource && current.equals(attributionChain)) {
+                    return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+                }
+            } else {
+                if (selfAccess) {
+                    return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+                } else if (fromDatasource && current.equals(attributionChain.getNext())) {
+                    return AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+                } else if (current.getNext() == null) {
+                    return AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+                }
+            }
+            if (fromDatasource && current.equals(attributionChain)) {
+                return AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+            }
+            return AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY;
+        }
+
         private static int checkOp(@NonNull Context context, @NonNull int op,
                 @NonNull AttributionSource attributionSource, @Nullable String message,
                 boolean forDataDelivery, boolean startDataDelivery) {
@@ -5721,6 +5836,10 @@
                 return PermissionChecker.PERMISSION_HARD_DENIED;
             }
 
+            final int attributionChainId = (startDataDelivery)
+                    ? sAttributionChainIds.incrementAndGet()
+                    : AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+
             AttributionSource current = attributionSource;
             AttributionSource next = null;
 
@@ -5737,9 +5856,18 @@
                 // The access is for oneself if this is the single attribution source in the chain.
                 final boolean selfAccess = (next == null);
 
+                final int proxyAttributionFlags = (!skipCurrentChecks)
+                        ? resolveProxyAttributionFlags(attributionSource, current,
+                                /*fromDatasource*/ false, startDataDelivery, selfAccess)
+                        : AppOpsManager.ATTRIBUTION_FLAGS_NONE;
+                final int proxiedAttributionFlags = resolveProxiedAttributionFlags(
+                        attributionSource, next, /*fromDatasource*/ false, startDataDelivery,
+                        selfAccess);
+
                 final int opMode = performOpTransaction(context, op, current, message,
                         forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                        /*fromDatasource*/ false);
+                        /*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags,
+                        proxiedAttributionFlags, attributionChainId);
 
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
@@ -5758,10 +5886,13 @@
             }
         }
 
+        @SuppressWarnings("ConstantConditions")
         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) {
+                boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp,
+                @AttributionFlags int proxyAttributionFlags,
+                @AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
             // 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
@@ -5793,36 +5924,75 @@
                 if (resolvedAttributionSource.getPackageName() == null) {
                     return AppOpsManager.MODE_ERRORED;
                 }
+                // 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.
+                int startedOp = op;
+                int checkedOpResult = MODE_ALLOWED;
+                int startedOpResult;
+
+                // If the datasource wants to attribute to another app op we need to
+                // make sure the op for the permission and the attributed ops allow
+                // the operation. We return the less permissive of the two and check
+                // the permission op while start the attributed op.
+                if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
+                    checkedOpResult = appOpsManager.checkOpNoThrow(op,
+                            resolvedAttributionSource.getUid(), resolvedAttributionSource
+                                    .getPackageName());
+                    if (checkedOpResult == MODE_ERRORED) {
+                        return checkedOpResult;
+                    }
+                    startedOp = attributedOp;
+                }
                 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(),
+                        startedOpResult = appOpsManager.startOpNoThrow(
+                                resolvedAttributionSource.getToken(), startedOp,
+                                resolvedAttributionSource.getUid(),
                                 resolvedAttributionSource.getPackageName(),
                                 /*startIfModeDefault*/ false,
                                 resolvedAttributionSource.getAttributionTag(),
-                                message);
+                                message, proxyAttributionFlags, attributionChainId);
                     } 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);
+                        startedOpResult = appOpsManager.startProxyOpNoThrow(attributedOp,
+                                attributionSource, message, skipProxyOperation,
+                                proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                     }
                 } else {
-                    return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
-                            skipProxyOperation);
+                    startedOpResult = appOpsManager.startProxyOpNoThrow(startedOp,
+                            resolvedAttributionSource, message, skipProxyOperation,
+                            proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
                 }
+                return Math.max(checkedOpResult, startedOpResult);
             } else {
                 final AttributionSource resolvedAttributionSource = resolveAttributionSource(
                         context, accessorSource);
                 if (resolvedAttributionSource.getPackageName() == null) {
                     return AppOpsManager.MODE_ERRORED;
                 }
+                int notedOp = op;
+                int checkedOpResult = MODE_ALLOWED;
+                int notedOpResult;
+
+                // If the datasource wants to attribute to another app op we need to
+                // make sure the op for the permission and the attributed ops allow
+                // the operation. We return the less permissive of the two and check
+                // the permission op while start the attributed op.
+                if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
+                    checkedOpResult = appOpsManager.checkOpNoThrow(op,
+                            resolvedAttributionSource.getUid(), resolvedAttributionSource
+                                    .getPackageName());
+                    if (checkedOpResult == MODE_ERRORED) {
+                        return checkedOpResult;
+                    }
+                    notedOp = attributedOp;
+                }
                 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
@@ -5830,7 +6000,8 @@
                     // 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(),
+                        notedOpResult = appOpsManager.noteOpNoThrow(notedOp,
+                                resolvedAttributionSource.getUid(),
                                 resolvedAttributionSource.getPackageName(),
                                 resolvedAttributionSource.getAttributionTag(),
                                 message);
@@ -5839,13 +6010,14 @@
                                 + " platform defined runtime permission "
                                 + AppOpsManager.opToPermission(op) + " while not having "
                                 + Manifest.permission.UPDATE_APP_OPS_STATS);
-                        return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
-                                skipProxyOperation);
+                        notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp, attributionSource,
+                                message, skipProxyOperation);
                     }
                 } else {
-                    return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
-                            skipProxyOperation);
+                    notedOpResult = appOpsManager.noteProxyOpNoThrow(notedOp,
+                            resolvedAttributionSource, message, skipProxyOperation);
                 }
+                return Math.max(checkedOpResult, notedOpResult);
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 2cfbf26..607bc56 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.SyncNotedAppOp;
 import android.app.role.RoleManager;
@@ -39,6 +40,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.NonaFunction;
@@ -46,6 +48,7 @@
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintFunction;
 import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
 
 import java.util.List;
@@ -179,32 +182,37 @@
     public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
             @Nullable String packageName, @Nullable String attributionTag,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-            boolean shouldCollectMessage, @NonNull NonaFunction<IBinder, Integer, Integer, String,
-                    String, Boolean, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String,
+                    String, Boolean, Boolean, String, Boolean, Integer, Integer,
+            SyncNotedAppOp> superImpl) {
         return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag),
                 uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage);
+                message, shouldCollectMessage, attributionFlags, attributionChainId);
     }
 
     @Override
-    public SyncNotedAppOp startProxyOperation(IBinder token, int code,
+    public SyncNotedAppOp startProxyOperation(int code,
             @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-            boolean skipProxyOperation, @NonNull OctFunction<IBinder, Integer, AttributionSource,
-                    Boolean, Boolean, String, Boolean, Boolean, SyncNotedAppOp> superImpl) {
-        return superImpl.apply(token, resolveDatasourceOp(code, attributionSource.getUid(),
+            boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+            @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+            @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
+                    Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+        return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
                 attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, skipProxyOperation);
+                shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                proxiedAttributionFlags, attributionChainId);
     }
 
     @Override
-    public void finishProxyOperation(IBinder clientId, int code,
-            @NonNull AttributionSource attributionSource,
-            @NonNull TriFunction<IBinder, Integer, AttributionSource, Void> superImpl) {
-        superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
+    public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+            boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
+            Boolean, Void> superImpl) {
+        superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
-                attributionSource);
+                attributionSource, skipProxyOperation);
     }
 
     private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b7ba8f8..81992d8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5013,28 +5013,6 @@
     }
 
     /**
-     * Re-parent the DisplayContent's top surface, {@link #mSurfaceControl} to the specified
-     * SurfaceControl.
-     *
-     * @param win The window which owns the SurfaceControl. This indicates the z-order of the
-     *            windows of this display against the windows on the parent display.
-     * @param sc The new SurfaceControl, where the DisplayContent's surfaces will be re-parented to.
-     */
-    void reparentDisplayContent(WindowState win, SurfaceControl sc) {
-        if (mParentWindow != null) {
-            mParentWindow.removeEmbeddedDisplayContent(this);
-        }
-        mParentWindow = win;
-        mParentWindow.addEmbeddedDisplayContent(this);
-        mParentSurfaceControl = sc;
-        if (mPortalWindowHandle == null) {
-            mPortalWindowHandle = createPortalWindowHandle(sc.toString());
-        }
-        getPendingTransaction().setInputWindowInfo(sc, mPortalWindowHandle)
-                .reparent(mSurfaceControl, sc);
-    }
-
-    /**
      * Get the window which owns the surface that this DisplayContent is re-parented to.
      *
      * @return the parent window.
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 2feb8a7..e7ad18f 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -658,6 +658,8 @@
         if (!mNavigationBarAttachedToApp) {
             return;
         }
+        mNavigationBarAttachedToApp = false;
+
         if (mStatusBar != null) {
             mStatusBar.setNavigationBarLumaSamplingEnabled(mDisplayId, true);
         }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index c6cd560..e282012 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -609,11 +609,6 @@
     }
 
     @Override
-    public void reparentDisplayContent(IWindow window, SurfaceControl sc, int displayId) {
-        mService.reparentDisplayContent(window, sc, displayId);
-    }
-
-    @Override
     public void updateDisplayContentLocation(IWindow window, int x, int y, int displayId) {
         mService.updateDisplayContentLocation(window, x, y, displayId);
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 039422d..82d1c04 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4967,7 +4967,8 @@
     }
 
     private boolean canBeOrganized() {
-        if (mForceNotOrganized) {
+        if (mForceNotOrganized || !mAtmService.mTaskOrganizerController
+                .isSupportedWindowingMode(getWindowingMode())) {
             return false;
         }
         // All root tasks can be organized
@@ -4982,12 +4983,13 @@
 
     @Override
     boolean showSurfaceOnCreation() {
+        if (mCreatedByOrganizer) {
+            // Tasks created by the organizer are default visible because they can synchronously
+            // update the leash before new children are added to the task.
+            return true;
+        }
         // Organized tasks handle their own surface visibility
-        final boolean willBeOrganized =
-                mAtmService.mTaskOrganizerController.isSupportedWindowingMode(getWindowingMode())
-                && isRootTask();
-        return !mAtmService.getTransitionController().isShellTransitionsEnabled()
-                || !willBeOrganized;
+        return !canBeOrganized();
     }
 
     @Override
@@ -5003,22 +5005,8 @@
     }
 
     void setHasBeenVisible(boolean hasBeenVisible) {
-        final boolean prevHasBeenVisible = mHasBeenVisible;
         mHasBeenVisible = hasBeenVisible;
         if (hasBeenVisible) {
-            // If the task is not yet visible when it is added to the task organizer, then we should
-            // hide it to allow the task organizer to show it when it is properly reparented. We
-            // skip this for tasks created by the organizer because they can synchronously update
-            // the leash before new children are added to the task.  Also skip this if the task
-            // has already been sent to the organizer which can happen before the first draw if
-            // an existing task is reported to the organizer when it first registers.
-            if (!mAtmService.getTransitionController().isShellTransitionsEnabled()
-                    && !mCreatedByOrganizer && !mTaskAppearedSent
-                    && mTaskOrganizer != null && !prevHasBeenVisible) {
-                getSyncTransaction().hide(getSurfaceControl());
-                commitPendingTransaction();
-            }
-
             if (!mDeferTaskAppear) sendTaskAppeared();
             if (!isRootTask()) {
                 getRootTask().setHasBeenVisible(true);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index cc8ee60..f23028f 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -215,23 +215,15 @@
             }
         }
 
-        SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
-            SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason);
-            if (!task.mCreatedByOrganizer && !visible) {
-                // To prevent flashes, we hide the task prior to sending the leash to the
-                // task org if the task has previously hidden (ie. when entering PIP)
-                mTransaction.hide(outSurfaceControl);
-                mTransaction.apply();
-            }
-            return outSurfaceControl;
+        SurfaceControl prepareLeash(Task task, String reason) {
+            return new SurfaceControl(task.getSurfaceControl(), reason);
         }
 
         void onTaskAppeared(Task task) {
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task appeared taskId=%d", task.mTaskId);
-            final boolean visible = task.isVisible();
             final RunningTaskInfo taskInfo = task.getTaskInfo();
             try {
-                mTaskOrganizer.onTaskAppeared(taskInfo, prepareLeash(task, visible,
+                mTaskOrganizer.onTaskAppeared(taskInfo, prepareLeash(task,
                         "TaskOrganizerController.onTaskAppeared"));
             } catch (RemoteException e) {
                 Slog.e(TAG, "Exception sending onTaskAppeared callback", e);
@@ -331,7 +323,7 @@
             if (!mOrganizedTasks.contains(t)) {
                 mOrganizedTasks.add(t);
             }
-            return mOrganizer.prepareLeash(t, t.isVisible(), reason);
+            return mOrganizer.prepareLeash(t, reason);
         }
 
         private boolean addTask(Task t) {
@@ -434,7 +426,6 @@
     // Set of organized tasks (by taskId) that dispatch back pressed to their organizers
     private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet();
 
-    private SurfaceControl.Transaction mTransaction;
     private RunningTaskInfo mTmpTaskInfo;
     private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
@@ -479,13 +470,6 @@
             synchronized (mGlobalLock) {
                 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d",
                         organizer.asBinder(), uid);
-
-                // Defer initializing the transaction since the transaction factory can be set up
-                // by the tests after construction of the controller
-                if (mTransaction == null) {
-                    mTransaction = mService.mWindowManager.mTransactionFactory.get();
-                }
-
                 if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
                     mTaskOrganizers.add(organizer);
                     mTaskOrganizerStates.put(organizer.asBinder(),
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4a9c1bb..1042a0b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6971,32 +6971,6 @@
         }
     }
 
-    /** @see Session#reparentDisplayContent(IWindow, SurfaceControl, int)  */
-    void reparentDisplayContent(IWindow client, SurfaceControl sc, int displayId) {
-        checkCallerOwnsDisplay(displayId);
-
-        synchronized (mGlobalLock) {
-            int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                final WindowState win = windowForClientLocked(null, client, false);
-                if (win == null) {
-                    ProtoLog.w(WM_ERROR, "Bad requesting window %s", client);
-                    return;
-                }
-                getDisplayContentOrCreate(displayId, null).reparentDisplayContent(win, sc);
-                // Notifies AccessibilityController to re-compute the window observer of
-                // this embedded display
-                if (mAccessibilityController != null) {
-                    mAccessibilityController.handleWindowObserverOfEmbeddedDisplay(
-                            displayId, win, uid);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
     /** @see Session#updateDisplayContentLocation(IWindow, int, int, int)  */
     void updateDisplayContentLocation(IWindow client, int x, int y, int displayId) {
         checkCallerOwnsDisplay(displayId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 20a992d2..b28297e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -258,6 +258,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.utils.CoordinateTransforms;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -638,6 +639,7 @@
     private PowerManager.WakeLock mDrawLock;
 
     private final Rect mTmpRect = new Rect();
+    private final Rect mTmpRect2 = new Rect();
     private final Point mTmpPoint = new Point();
 
     private final Transaction mTmpTransaction;
@@ -1160,13 +1162,14 @@
 
     /**
      * @return {@code true} if the application runs in size compatibility mode or has an app level
-     * scaling override set.
+     * scaling override set. This method always returns {@code false} on child window because it
+     * should follow parent's scale.
      * @see CompatModePackages#getCompatScale
      * @see android.content.res.CompatibilityInfo#supportsScreen
      * @see ActivityRecord#hasSizeCompatBounds()
      */
     boolean hasCompatScale() {
-        return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord);
+        return (mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord)) && !mIsChildWindow;
     }
 
     /**
@@ -1338,7 +1341,8 @@
                 }
             }
 
-            layoutDisplayFrame = new Rect(windowFrames.mDisplayFrame);
+            layoutDisplayFrame = mTmpRect2;
+            layoutDisplayFrame.set(windowFrames.mDisplayFrame);
             windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame);
             layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left;
             layoutYDiff = mInsetFrame.top - windowFrames.mContainingFrame.top;
@@ -3840,8 +3844,9 @@
 
         fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
                 true /* useLatestConfig */, false /* relayoutVisible */);
-        final boolean reportDraw = drawPending || useBLASTSync() || !mRedrawForSyncReported;
-        final boolean forceRelayout = reportOrientation || isDragResizeChanged() || !mRedrawForSyncReported;
+        final boolean syncRedraw = shouldSendRedrawForSync();
+        final boolean reportDraw = syncRedraw || drawPending;
+        final boolean forceRelayout = syncRedraw || reportOrientation || isDragResizeChanged();
         final DisplayContent displayContent = getDisplayContent();
         final boolean alwaysConsumeSystemBars =
                 displayContent.getDisplayPolicy().areSystemBarsForcedShownLw(this);
@@ -4441,6 +4446,22 @@
             h = Math.min(h, ph);
         }
 
+        if (mIsChildWindow) {
+            final WindowState parent = getTopParentWindow();
+            if (parent.hasCompatScale()) {
+                // Scale the containing and display frames because they are in screen coordinates.
+                // The position of frames are already relative to parent so only size is scaled.
+                mTmpRect.set(containingFrame);
+                containingFrame = mTmpRect;
+                CoordinateTransforms.scaleRectSize(containingFrame, parent.mInvGlobalScale);
+                if (fitToDisplay) {
+                    mTmpRect2.set(displayFrame);
+                    displayFrame = mTmpRect2;
+                    CoordinateTransforms.scaleRectSize(displayFrame, parent.mInvGlobalScale);
+                }
+            }
+        }
+
         // Set mFrame
         Gravity.apply(mAttrs.gravity, w, h, containingFrame,
                 (int) (x + mAttrs.horizontalMargin * pw),
@@ -5939,10 +5960,14 @@
      * for Windows involved in these Syncs
      */
     private boolean shouldSendRedrawForSync() {
+        if (mRedrawForSyncReported) {
+            return false;
+        }
         final Task task = getTask();
-        if (task != null && task.getMainWindowSizeChangeTransaction() != null)
-            return !mRedrawForSyncReported;
-        return useBLASTSync() && !mRedrawForSyncReported;
+        if (task != null && task.getMainWindowSizeChangeTransaction() != null) {
+            return true;
+        }
+        return useBLASTSync();
     }
 
     void requestRedrawForSync() {
diff --git a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
index a2f37a5..6d8e07a 100644
--- a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
+++ b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
@@ -152,4 +152,10 @@
         transform.mapRect(tmp);
         inOutRect.set((int) tmp.left, (int) tmp.top, (int) tmp.right, (int) tmp.bottom);
     }
+
+    /** Scales the rect without changing its position. */
+    public static void scaleRectSize(Rect inOutRect, float scale) {
+        inOutRect.right = inOutRect.left + (int) (inOutRect.width() * scale + .5f);
+        inOutRect.bottom = inOutRect.top + (int) (inOutRect.height() * scale + .5f);
+    }
 }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 64b9a63..24e11af 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -31,7 +31,10 @@
     name: "FrameworksMockingServicesTests",
     defaults: ["FrameworkMockingServicesTests-jni-defaults"],
 
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     static_libs: [
         "services.core",
@@ -41,6 +44,7 @@
         "service-jobscheduler",
         "service-permission.impl",
         "service-blobstore",
+        "service-appsearch",
         "androidx.test.runner",
         "androidx.test.ext.truth",
         "mockito-target-extended-minus-junit4",
diff --git a/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
new file mode 100644
index 0000000..c486028
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.DeviceConfig;
+
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Tests for {@link AppSearchConfig}.
+ *
+ * <p>Build/Install/Run: atest FrameworksMockingServicesTests:AppSearchConfigTest
+ */
+public class AppSearchConfigTest {
+    @Rule
+    public final TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Test
+    public void testDefaultValues_allCachedValue() {
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis()).isEqualTo(
+                AppSearchConfig.DEFAULT_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS);
+        assertThat(appSearchConfig.getCachedSamplingIntervalDefault()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+    }
+
+    @Test
+    public void testCustomizedValue_minTimeIntervalBetweenSamplesMillis() {
+        final long minTimeIntervalBetweenSamplesMillis = -1;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                Long.toString(minTimeIntervalBetweenSamplesMillis),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis()).isEqualTo(
+                minTimeIntervalBetweenSamplesMillis);
+    }
+
+    @Test
+    public void testCustomizedValueOverride_minTimeIntervalBetweenSamplesMillis() {
+        long minTimeIntervalBetweenSamplesMillis = -1;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                Long.toString(minTimeIntervalBetweenSamplesMillis),
+                false);
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        minTimeIntervalBetweenSamplesMillis = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                Long.toString(minTimeIntervalBetweenSamplesMillis),
+                false);
+
+        assertThat(appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis()).isEqualTo(
+                minTimeIntervalBetweenSamplesMillis);
+    }
+
+    @Test
+    public void testCustomizedValue_allSamplingIntervals() {
+        final int samplingIntervalDefault = -1;
+        final int samplingIntervalPutDocumentStats = -2;
+        final int samplingIntervalBatchCallStats = -3;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalDefault()).isEqualTo(
+                samplingIntervalDefault);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalBatchCallStats);
+    }
+
+    @Test
+    public void testCustomizedValueOverride_allSamplingIntervals() {
+        int samplingIntervalDefault = -1;
+        int samplingIntervalPutDocumentStats = -2;
+        int samplingIntervalBatchCallStats = -3;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        // Overrides
+        samplingIntervalDefault = -4;
+        samplingIntervalPutDocumentStats = -5;
+        samplingIntervalBatchCallStats = -6;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalDefault()).isEqualTo(
+                samplingIntervalDefault);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalBatchCallStats);
+    }
+
+    /**
+     * Tests if we fall back to {@link AppSearchConfig#DEFAULT_SAMPLING_INTERVAL} if both default
+     * sampling
+     * interval and custom value are not set in DeviceConfig, and there is some other sampling
+     * interval
+     * set.
+     */
+    @Test
+    public void testFallbackToDefaultSamplingValue_useHardCodedDefault() {
+        final int samplingIntervalPutDocumentStats = -1;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+    }
+
+    // Tests if we fall back to configured default sampling interval if custom value is not set in
+    // DeviceConfig.
+    @Test
+    public void testFallbackDefaultSamplingValue_useConfiguredDefault() {
+        final int samplingIntervalPutDocumentStats = -1;
+        final int samplingIntervalDefault = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalDefault);
+    }
+
+    // Tests that cached values should reflect latest values in DeviceConfig.
+    @Test
+    public void testFallbackDefaultSamplingValue_defaultValueChanged() {
+        int samplingIntervalPutDocumentStats = -1;
+        int samplingIntervalDefault = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        // Sampling values changed.
+        samplingIntervalPutDocumentStats = -3;
+        samplingIntervalDefault = -4;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+                Integer.toString(samplingIntervalPutDocumentStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
+                samplingIntervalPutDocumentStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalDefault);
+    }
+
+    // Tests default sampling interval won't affect custom sampling intervals if they are set.
+    @Test
+    public void testShouldNotFallBack_ifValueConfigured() {
+        int samplingIntervalDefault = -1;
+        int samplingIntervalBatchCallStats = -2;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
+                Integer.toString(samplingIntervalBatchCallStats),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        // Default sampling interval changed.
+        samplingIntervalDefault = -3;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
+                Integer.toString(samplingIntervalDefault),
+                false);
+
+        assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
+                samplingIntervalBatchCallStats);
+    }
+
+    @Test
+    public void testNotUsable_afterClose() {
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        appSearchConfig.close();
+
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedMinTimeIntervalBetweenSamplesMillis());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalDefault());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForBatchCallStats());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForPutDocumentStats());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index 98bc067..a2aaccc 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -20,6 +20,8 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -66,7 +68,8 @@
         // Verify that we got called for the op being active
         verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
-                eq(Process.myUid()), eq(getContext().getPackageName()), eq(true));
+                eq(Process.myUid()), eq(getContext().getPackageName()),
+                isNull(), eq(true), anyInt(), anyInt());
 
         // This should be the only callback we got
         verifyNoMoreInteractions(listener);
@@ -84,7 +87,8 @@
         // Verify that we got called for the op being active
         verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
-                eq(Process.myUid()), eq(getContext().getPackageName()), eq(false));
+                eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
+                eq(false), anyInt(), anyInt());
 
         // Verify that the op is not active
         assertThat(appOpsManager.isOperationActive(AppOpsManager.OP_CAMERA,
@@ -121,7 +125,8 @@
         // We should get the callback again (and since we reset the listener, we therefore expect 1)
         verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
-                eq(Process.myUid()), eq(getContext().getPackageName()), eq(true));
+                eq(Process.myUid()), eq(getContext().getPackageName()), isNull(),
+                eq(true), anyInt(), anyInt());
 
         // Finish up
         appOpsManager.finishOp(AppOpsManager.OP_CAMERA);
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
index 747dd1d..734f05a 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
@@ -46,7 +46,7 @@
 
 public class PlatformLoggerTest {
     private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
-    private static final int TEST_DEFAULT_SAMPLING_RATIO = 10;
+    private static final int TEST_DEFAULT_SAMPLING_INTERVAL = 10;
     private static final String TEST_PACKAGE_NAME = "packageName";
     private MockPackageManager mMockPackageManager = new MockPackageManager();
     private Context mContext;
@@ -72,63 +72,63 @@
     }
 
     @Test
-    public void testCreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() {
+    public void testCreateExtraStatsLocked_nullSamplingIntervalMap_returnsDefault() {
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        TEST_DEFAULT_SAMPLING_RATIO,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        TEST_DEFAULT_SAMPLING_INTERVAL,
+                        /*samplingIntervals=*/ new SparseIntArray()));
 
-        // Make sure default sampling ratio is used if samplingMap is not provided.
+        // Make sure default sampling interval is used if samplingMap is not provided.
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_UNKNOWN).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_UNKNOWN).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_SEARCH).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_SEARCH).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
     }
 
 
     @Test
-    public void testCreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() {
-        int putDocumentSamplingRatio = 1;
-        int querySamplingRatio = 2;
-        final SparseIntArray samplingRatios = new SparseIntArray();
-        samplingRatios.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingRatio);
-        samplingRatios.put(CallStats.CALL_TYPE_SEARCH, querySamplingRatio);
+    public void testCreateExtraStatsLocked_with_samplingIntervalMap_returnsConfigured() {
+        int putDocumentSamplingInterval = 1;
+        int querySamplingInterval = 2;
+        final SparseIntArray samplingIntervals = new SparseIntArray();
+        samplingIntervals.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingInterval);
+        samplingIntervals.put(CallStats.CALL_TYPE_SEARCH, querySamplingInterval);
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        TEST_DEFAULT_SAMPLING_RATIO,
-                        samplingRatios));
+                        TEST_DEFAULT_SAMPLING_INTERVAL,
+                        samplingIntervals));
 
-        // The default sampling ratio should be used if no sampling ratio is
+        // The default sampling interval should be used if no sampling interval is
         // provided for certain call type.
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_INITIALIZE).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
-                TEST_DEFAULT_SAMPLING_RATIO);
+                CallStats.CALL_TYPE_FLUSH).mSamplingInterval).isEqualTo(
+                TEST_DEFAULT_SAMPLING_INTERVAL);
 
-        // The configured sampling ratio is used if sampling ratio is available
+        // The configured sampling interval is used if sampling interval is available
         // for certain call type.
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingRatio).isEqualTo(
-                putDocumentSamplingRatio);
+                CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingInterval).isEqualTo(
+                putDocumentSamplingInterval);
         assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
-                CallStats.CALL_TYPE_SEARCH).mSamplingRatio).isEqualTo(
-                querySamplingRatio);
+                CallStats.CALL_TYPE_SEARCH).mSamplingInterval).isEqualTo(
+                querySamplingInterval);
     }
 
     @Test
@@ -202,16 +202,16 @@
     }
 
     @Test
-    public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
-        final int samplingRatio = 1;
+    public void testShouldLogForTypeLocked_trueWhenSampleIntervalIsOne() {
+        final int samplingInterval = 1;
         final String testPackageName = "packageName";
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
 
         // Sample should always be logged for the first time if sampling is disabled(value is one).
         assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
@@ -220,18 +220,18 @@
     }
 
     @Test
-    public void testShouldLogForTypeLocked_falseWhenSampleRatioIsNegative() {
-        final int samplingRatio = -1;
+    public void testShouldLogForTypeLocked_falseWhenSampleIntervalIsNegative() {
+        final int samplingInterval = -1;
         final String testPackageName = "packageName";
         PlatformLogger logger = new PlatformLogger(
                 ApplicationProvider.getApplicationContext(),
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
 
-        // Makes sure sample will be excluded due to sampling if sample ratio is negative.
+        // Makes sure sample will be excluded due to sampling if sample interval is negative.
         assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
         // Skipped count should be 0 since it doesn't pass the sampling.
         assertThat(logger.createExtraStatsLocked(testPackageName,
@@ -241,7 +241,7 @@
     @Test
     public void testShouldLogForTypeLocked_falseWhenWithinCoolOffInterval() {
         // Next sample won't be excluded due to sampling.
-        final int samplingRatio = 1;
+        final int samplingInterval = 1;
         // Next sample would guaranteed to be too close.
         final int minTimeIntervalBetweenSamplesMillis = Integer.MAX_VALUE;
         final String testPackageName = "packageName";
@@ -250,8 +250,8 @@
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         minTimeIntervalBetweenSamplesMillis,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
         logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
 
         // Makes sure sample will be excluded due to rate limiting if samples are too close.
@@ -263,7 +263,7 @@
     @Test
     public void testShouldLogForTypeLocked_trueWhenOutsideOfCoolOffInterval() {
         // Next sample won't be excluded due to sampling.
-        final int samplingRatio = 1;
+        final int samplingInterval = 1;
         // Next sample would guaranteed to be included.
         final int minTimeIntervalBetweenSamplesMillis = 0;
         final String testPackageName = "packageName";
@@ -272,8 +272,8 @@
                 UserHandle.of(UserHandle.USER_NULL),
                 new PlatformLogger.Config(
                         minTimeIntervalBetweenSamplesMillis,
-                        samplingRatio,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        samplingInterval,
+                        /*samplingIntervals=*/ new SparseIntArray()));
         logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
 
         // Makes sure sample will be logged if it is not too close to previous sample.
@@ -292,8 +292,8 @@
                 mContext.getUser(),
                 new PlatformLogger.Config(
                         TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
-                        TEST_DEFAULT_SAMPLING_RATIO,
-                        /*samplingRatios=*/ new SparseIntArray()));
+                        TEST_DEFAULT_SAMPLING_INTERVAL,
+                        /*samplingIntervals=*/ new SparseIntArray()));
         mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
 
         //
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index 2162c0b..e811c1f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -5,22 +5,29 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+
 import static org.testng.Assert.assertThrows;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.IApplicationThread;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSourceState;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.CrossProfileAppsInternal;
@@ -34,6 +41,7 @@
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
 import android.permission.PermissionManager;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
@@ -411,6 +419,18 @@
         }
         mActivityInfo.exported = false;
 
+
+        // There's a bug in static mocking if the APK is large - so here is the next best thing...
+        doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext)
+                .getSystemServiceName(PermissionCheckerManager.class);
+        PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager)
+                .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any(
+                        AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+                        anyBoolean(), anyInt());
+        doReturn(permissionCheckerManager).when(mContext).getSystemService(
+                Context.PERMISSION_CHECKER_SERVICE);
+
         assertThrows(
                 SecurityException.class,
                 () ->
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 41506f6..39a59c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -519,12 +519,14 @@
                 eq(mDefaultDisplay.mDisplayId), eq(false));
         verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
         verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
+        assertTrue(mController.isNavigationBarAttachedToApp());
 
         mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
         verify(mController).restoreNavigationBarFromApp(eq(true));
         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
                 eq(mDefaultDisplay.mDisplayId), eq(true));
         verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
+        assertFalse(mController.isNavigationBarAttachedToApp());
     }
 
     @Test
@@ -541,6 +543,7 @@
                 eq(mDefaultDisplay.mDisplayId), eq(false));
         verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
         verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
+        assertTrue(mController.isNavigationBarAttachedToApp());
 
         final WindowContainer parent = navToken.getParent();
 
@@ -550,6 +553,7 @@
                 eq(mDefaultDisplay.mDisplayId), eq(true));
         verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
         verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
+        assertFalse(mController.isNavigationBarAttachedToApp());
     }
 
     @Test
@@ -566,6 +570,7 @@
                 eq(mDefaultDisplay.mDisplayId), eq(false));
         verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
         verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
+        assertTrue(mController.isNavigationBarAttachedToApp());
 
         final WindowContainer parent = navToken.getParent();
 
@@ -575,6 +580,7 @@
         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
                 eq(mDefaultDisplay.mDisplayId), eq(true));
         verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
+        assertFalse(mController.isNavigationBarAttachedToApp());
     }
 
     @Test
@@ -607,6 +613,7 @@
                 eq(mDefaultDisplay.mDisplayId), eq(false));
         verify(navWindow).setSurfaceTranslationY(-secondary.getBounds().top);
         verify(transaction).reparent(navToken.getSurfaceControl(), secondary.getSurfaceControl());
+        assertTrue(mController.isNavigationBarAttachedToApp());
         reset(navWindow);
 
         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
@@ -616,6 +623,7 @@
         verify(navWindow).setSurfaceTranslationY(0);
         verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
         verify(mController).restoreNavigationBarFromApp(eq(false));
+        assertFalse(mController.isNavigationBarAttachedToApp());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 152a575..92b670e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -35,6 +35,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -577,21 +578,43 @@
         spyOn(cmp);
         doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt());
         final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win");
-        makeWindowVisible(w);
+        final WindowState child = createWindow(w, TYPE_APPLICATION_PANEL, "child");
+
+        assertTrue(w.hasCompatScale());
+        assertFalse(child.hasCompatScale());
+
+        makeWindowVisible(w, child);
         w.setRequestedSize(100, 200);
+        child.setRequestedSize(50, 100);
+        child.mAttrs.width = child.mAttrs.height = 0;
+        w.mAttrs.x = w.mAttrs.y = 100;
         w.mAttrs.width = w.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
         w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
+        child.mAttrs.gravity = Gravity.CENTER;
         DisplayContentTests.performLayout(mDisplayContent);
 
-        // Frame on screen = 100x200. Compat frame on client = 50x100.
+        // Frame on screen = 200x400 (200, 200 - 400, 600). Compat frame on client = 100x200.
         final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
         unscaledCompatFrame.scale(overrideScale);
+        final Rect parentFrame = w.getFrame();
         assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
 
+        final Rect childFrame = child.getFrame();
+        assertEquals(childFrame, child.getWindowFrames().mCompatFrame);
+        // Child frame = 50x100 (225, 250 - 275, 350) according to Gravity.CENTER.
+        final int childX = parentFrame.left + child.mRequestedWidth / 2;
+        final int childY = parentFrame.top + child.mRequestedHeight / 2;
+        final Rect expectedChildFrame = new Rect(childX, childY, childX + child.mRequestedWidth,
+                childY + child.mRequestedHeight);
+        assertEquals(expectedChildFrame, childFrame);
+
         // Surface should apply the scale.
         w.prepareSurfaces();
         verify(w.getPendingTransaction()).setMatrix(w.getSurfaceControl(),
                 overrideScale, 0, 0, overrideScale);
+        // Child surface inherits parent's scale, so it doesn't need to scale.
+        verify(child.getPendingTransaction(), never()).setMatrix(any(), anyInt(), anyInt(),
+                anyInt(), anyInt());
 
         // According to "dp * density / 160 = px", density is scaled and the size in dp is the same.
         final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked(
@@ -691,39 +714,6 @@
     }
 
     @Test
-    public void testGetTransformationMatrix() {
-        final int PARENT_WINDOW_OFFSET = 1;
-        final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2;
-        final int WINDOW_OFFSET = 3;
-        final float OFFSET_SUM =
-                PARENT_WINDOW_OFFSET + DISPLAY_IN_PARENT_WINDOW_OFFSET + WINDOW_OFFSET;
-
-        final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
-
-        final DisplayContent dc = createNewDisplay();
-        win0.getFrame().offsetTo(PARENT_WINDOW_OFFSET, 0);
-        dc.reparentDisplayContent(win0, win0.getSurfaceControl());
-        dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0);
-
-        final float[] values = new float[9];
-        final Matrix matrix = new Matrix();
-        final SurfaceControl.Transaction t = spy(StubTransaction.class);
-        final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
-        win1.mHasSurface = true;
-        win1.mSurfaceControl = mock(SurfaceControl.class);
-        win1.mAttrs.surfaceInsets.set(1, 2, 3, 4);
-        win1.getFrame().offsetTo(WINDOW_OFFSET, 0);
-        // Simulate layout
-        win1.mRelayoutCalled = true;
-        win1.updateSurfacePosition(t);
-        win1.getTransformationMatrix(values, matrix);
-
-        matrix.getValues(values);
-        assertEquals(OFFSET_SUM, values[Matrix.MTRANS_X], 0f);
-        assertEquals(0f, values[Matrix.MTRANS_Y], 0f);
-    }
-
-    @Test
     public void testCantReceiveTouchDuringRecentsAnimation() {
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");