Merge "Retry on unavailable." into sc-dev
diff --git a/Android.bp b/Android.bp
index 685c69d..8db5589 100644
--- a/Android.bp
+++ b/Android.bp
@@ -325,6 +325,7 @@
         "tv_tuner_resource_manager_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
         "modules-utils-os",
+        "framework-permission-aidl-java",
     ],
 }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 7ca77d4..3f6e8a5 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -16,6 +16,7 @@
 package com.android.server.appsearch;
 
 import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
+import static android.os.Process.INVALID_UID;
 import static android.os.UserHandle.USER_NULL;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -53,7 +54,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
@@ -128,6 +128,17 @@
         mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
                 new IntentFilter(Intent.ACTION_USER_REMOVED), /*broadcastPermission=*/ null,
                 /*scheduler=*/ null);
+
+        //TODO(b/145759910) Add a direct callback when user clears the data instead of relying on
+        // broadcasts
+        IntentFilter packageChangedFilter = new IntentFilter();
+        packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        packageChangedFilter.addDataScheme("package");
+        packageChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
+                packageChangedFilter, /*broadcastPermission=*/ null,
+                /*scheduler=*/ null);
     }
 
     private class UserActionReceiver extends BroadcastReceiver {
@@ -135,15 +146,15 @@
         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
             switch (intent.getAction()) {
                 case Intent.ACTION_USER_REMOVED:
-                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                    int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                     if (userId == USER_NULL) {
-                        Slog.e(TAG, "userId is missing in the intent: " + intent);
+                        Log.e(TAG, "userId is missing in the intent: " + intent);
                         return;
                     }
                     handleUserRemoved(userId);
                     break;
                 default:
-                    Slog.e(TAG, "Received unknown intent: " + intent);
+                    Log.e(TAG, "Received unknown intent: " + intent);
             }
         }
     }
@@ -163,9 +174,52 @@
         try {
             mImplInstanceManager.removeAppSearchImplForUser(userId);
             mLoggerInstanceManager.removePlatformLoggerForUser(userId);
-            Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
+            Log.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
         } catch (Throwable t) {
-            Slog.e(TAG, "Unable to remove data for user: " + userId, t);
+            Log.e(TAG, "Unable to remove data for user: " + userId, t);
+        }
+    }
+
+    private class PackageChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_PACKAGE_FULLY_REMOVED:
+                case Intent.ACTION_PACKAGE_DATA_CLEARED:
+                    String packageName = intent.getData().getSchemeSpecificPart();
+                    if (packageName == null) {
+                        Log.e(TAG, "Package name is missing in the intent: " + intent);
+                        return;
+                    }
+                    int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
+                    if (uid == INVALID_UID) {
+                        Log.e(TAG, "uid is missing in the intent: " + intent);
+                        return;
+                    }
+                    handlePackageRemoved(packageName, uid);
+                    break;
+                default:
+                    Log.e(TAG, "Received unknown intent: " + intent);
+            }
+        }
+    }
+
+    private void handlePackageRemoved(String packageName, int uid) {
+        int userId = UserHandle.getUserId(uid);
+        try {
+            if (isUserLocked(userId)) {
+                //TODO(b/186151459) clear the uninstalled package data when user is unlocked.
+                return;
+            }
+            if (ImplInstanceManager.getAppSearchDir(userId).exists()) {
+                // Only clear the package's data if AppSearch exists for this user.
+                AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(mContext,
+                        userId);
+                //TODO(b/145759910) clear visibility setting for package.
+                impl.clearPackageData(packageName);
+            }
+        } catch (Throwable t) {
+            Log.e(TAG, "Unable to remove data for package: " + packageName, t);
         }
     }
 
@@ -177,17 +231,20 @@
     }
 
     private void verifyUserUnlocked(int callingUserId) {
+        if (isUserLocked(callingUserId)) {
+            throw new IllegalStateException("User " + callingUserId + " is locked or not running.");
+        }
+    }
+
+    private boolean isUserLocked(int callingUserId) {
         synchronized (mUnlockedUserIdsLocked) {
             // First, check the local copy.
             if (mUnlockedUserIdsLocked.contains(callingUserId)) {
-                return;
+                return false;
             }
             // If the local copy says the user is locked, check with UM for the actual state,
             // since the user might just have been unlocked.
-            if (!mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(callingUserId))) {
-                throw new IllegalStateException(
-                        "User " + callingUserId + " is locked or not running.");
-            }
+            return !mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(callingUserId));
         }
     }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index af39b79..45023f9 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -73,6 +73,15 @@
     }
 
     /**
+     * Returns AppSearch directory in the credential encrypted system directory for the given user.
+     *
+     * <p>This folder should only be accessed after unlock.
+     */
+    public static File getAppSearchDir(@UserIdInt int userId) {
+        return new File(Environment.getDataSystemCeDirectory(userId), APP_SEARCH_DIR);
+    }
+
+    /**
      * Gets an instance of AppSearchImpl for the given user, or creates one if none exists.
      *
      * <p>If no AppSearchImpl instance exists for the unlocked user, Icing will be initialized and
@@ -139,15 +148,10 @@
 
     private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
             throws AppSearchException {
-        File appSearchDir = getAppSearchDir(context, userId);
+        File appSearchDir = getAppSearchDir(userId);
         return AppSearchImpl.create(appSearchDir, context, userId, mGlobalQuerierPackage);
     }
 
-    private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
-        // See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs
-        return new File(Environment.getDataSystemCeDirectory(userId), APP_SEARCH_DIR);
-    }
-
     /**
      * Returns the global querier package if it's a system package. Otherwise, empty string.
      *
diff --git a/cmds/bootanimation/BootAnimationUtil.cpp b/cmds/bootanimation/BootAnimationUtil.cpp
index 1e417e9..4f56e5a 100644
--- a/cmds/bootanimation/BootAnimationUtil.cpp
+++ b/cmds/bootanimation/BootAnimationUtil.cpp
@@ -49,7 +49,14 @@
     }
 
     property_get("ro.boot.quiescent", value, "0");
-    return atoi(value) > 0;
+    if (atoi(value) > 0) {
+        // Only show the bootanimation for quiescent boots if this system property is set to enabled
+        if (!property_get_bool("ro.bootanim.quiescent.enabled", false)) {
+            return true;
+        }
+    }
+
+    return false;
 }
 
 void waitForSurfaceFlinger() {
diff --git a/core/api/current.txt b/core/api/current.txt
index c19a89e..4cdc519 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9,6 +9,7 @@
     ctor public Manifest.permission();
     field public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
     field public static final String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION";
+    field public static final String ACCESS_BLOBS_ACROSS_USERS = "android.permission.ACCESS_BLOBS_ACROSS_USERS";
     field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
     field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
     field public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
@@ -18046,7 +18047,7 @@
   }
 
   public final class CameraExtensionCharacteristics {
-    method @Nullable public android.util.Range<java.lang.Long> getEstimatedCaptureLatencyRange(int, @NonNull android.util.Size, int);
+    method @Nullable public android.util.Range<java.lang.Long> getEstimatedCaptureLatencyRangeMillis(int, @NonNull android.util.Size, int);
     method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>);
     method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
     method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
@@ -52835,7 +52836,8 @@
   }
 
   public final class TranslationManager {
-    method public void addOnDeviceTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
+    method public void addOnDeviceTranslationCapabilityUpdateListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.translation.TranslationCapability>);
+    method @Deprecated public void addOnDeviceTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
     method @Deprecated public void addTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
     method @Nullable @WorkerThread public android.view.translation.Translator createOnDeviceTranslator(@NonNull android.view.translation.TranslationContext);
     method @Deprecated @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationContext);
@@ -52843,7 +52845,8 @@
     method @Nullable public android.app.PendingIntent getOnDeviceTranslationSettingsActivityIntent();
     method @Deprecated @NonNull @WorkerThread public java.util.Set<android.view.translation.TranslationCapability> getTranslationCapabilities(int, int);
     method @Deprecated @Nullable public android.app.PendingIntent getTranslationSettingsActivityIntent();
-    method public void removeOnDeviceTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
+    method public void removeOnDeviceTranslationCapabilityUpdateListener(@NonNull java.util.function.Consumer<android.view.translation.TranslationCapability>);
+    method @Deprecated public void removeOnDeviceTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
     method @Deprecated public void removeTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
   }
 
@@ -56792,6 +56795,7 @@
   public interface SplashScreen {
     method public void clearOnExitAnimationListener();
     method public void setOnExitAnimationListener(@NonNull android.window.SplashScreen.OnExitAnimationListener);
+    method public void setSplashScreenTheme(@StyleRes int);
   }
 
   public static interface SplashScreen.OnExitAnimationListener {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a4fdc33..fede5a5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3,7 +3,6 @@
 
   public static final class Manifest.permission {
     field public static final String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS";
-    field public static final String ACCESS_BLOBS_ACROSS_USERS = "android.permission.ACCESS_BLOBS_ACROSS_USERS";
     field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
     field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
     field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
@@ -1901,10 +1900,10 @@
 package android.bluetooth {
 
   public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile {
-    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public android.bluetooth.BufferConstraints getBufferConstraints();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getDynamicBufferSupport();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setBufferLengthMillis(int, int);
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BufferConstraints getBufferConstraints();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getDynamicBufferSupport();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setBufferLengthMillis(int, int);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; // 0x1
     field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; // 0x2
@@ -1919,8 +1918,8 @@
 
   public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile {
     method public void finalize();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isAudioPlaying(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
   }
@@ -2008,13 +2007,13 @@
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean connect(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
   public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public long getHiSyncId(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public long getHiSyncId(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
@@ -2024,9 +2023,9 @@
 
   public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
     method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
   }
 
@@ -2045,7 +2044,7 @@
 
   public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isTetheringOn();
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
@@ -2063,7 +2062,7 @@
   }
 
   public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
   }
@@ -10514,6 +10513,7 @@
     method public static int getMaxBundleSize();
     method public static int getMaxHotwordPhraseId();
     method public static int getMaxScore();
+    method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
     method public int getPersonalizedScore();
     method public int getScore();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -10528,6 +10528,7 @@
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setConfidenceLevel(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int);
+    method @NonNull public android.service.voice.HotwordDetectedResult.Builder setMediaSyncEvent(@NonNull android.media.MediaSyncEvent);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setPersonalizedScore(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setScore(int);
   }
@@ -10535,8 +10536,10 @@
   public abstract class HotwordDetectionService extends android.app.Service {
     ctor public HotwordDetectionService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, long, @NonNull android.service.voice.HotwordDetectionService.Callback);
-    method public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.service.voice.HotwordDetectionService.Callback);
+    method @Deprecated public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, long, @NonNull android.service.voice.HotwordDetectionService.Callback);
+    method public void onDetect(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload, long, @NonNull android.service.voice.HotwordDetectionService.Callback);
+    method @Deprecated public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.service.voice.HotwordDetectionService.Callback);
+    method public void onDetect(@NonNull android.service.voice.HotwordDetectionService.Callback);
     method public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle, @NonNull android.service.voice.HotwordDetectionService.Callback);
     method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
     field public static final int INITIALIZATION_STATUS_CUSTOM_ERROR_1 = 1; // 0x1
@@ -10582,7 +10585,8 @@
   public class VoiceInteractionService extends android.app.Service {
     method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.service.voice.HotwordDetector.Callback);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.service.voice.HotwordDetector.Callback);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.service.voice.HotwordDetector.Callback);
     method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
   }
 
@@ -14520,7 +14524,21 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId, @NonNull android.view.translation.UiTranslationSpec);
+  }
+
+  public final class UiTranslationSpec implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean shouldPadContentForCompat();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.UiTranslationSpec> CREATOR;
+  }
+
+  public static final class UiTranslationSpec.Builder {
+    ctor public UiTranslationSpec.Builder();
+    method @NonNull public android.view.translation.UiTranslationSpec build();
+    method @NonNull public android.view.translation.UiTranslationSpec.Builder setShouldPadContentForCompat(boolean);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d8c1387..4e929a8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2102,6 +2102,7 @@
     field public static final String AUTOMATIC_POWER_SAVE_MODE = "automatic_power_save_mode";
     field public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
     field public static final String DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW = "enable_non_resizable_multi_window";
+    field public static final String DISABLE_WINDOW_BLURS = "disable_window_blurs";
     field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
     field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
     field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
@@ -2779,7 +2780,6 @@
     method public default void holdLock(android.os.IBinder, int);
     method public default boolean isTaskSnapshotSupported();
     method public default void setDisplayImePolicy(int, int);
-    method public default void setForceCrossWindowBlurDisabled(boolean);
     method public default void setShouldShowSystemDecors(int, boolean);
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
     method public default boolean shouldShowSystemDecors(int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6e0b83f..59f794b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5968,20 +5968,6 @@
             // Update all affected Resources objects to use new ResourcesImpl
             mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs);
         }
-
-        ApplicationPackageManager.configurationChanged();
-
-        // Trigger a regular Configuration change event, only with a different assetsSeq number
-        // so that we actually call through to all components.
-        // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to
-        // store configurations per-process.
-        final Configuration config = mConfigurationController.getConfiguration();
-        Configuration newConfig = new Configuration();
-        newConfig.assetsSeq = (config != null ? config.assetsSeq : 0) + 1;
-        mConfigurationController.handleConfigurationChanged(newConfig, null /* compat */);
-
-        // Preserve windows to avoid black flickers when overlays change.
-        relaunchAllActivities(true /* preserveWindows */, "handleApplicationInfoChanged");
     }
 
     /**
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 759597c..4c1a363 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -531,7 +531,7 @@
         }
 
         final PasswordMetrics enteredMetrics = computeForPasswordOrPin(password, isPin);
-        return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics);
+        return validatePasswordMetrics(adminMetrics, minComplexity, enteredMetrics);
     }
 
     /**
@@ -539,15 +539,13 @@
      *
      * @param adminMetrics - minimum metrics to satisfy admin requirements.
      * @param minComplexity - minimum complexity imposed by the requester.
-     * @param isPin - whether it is PIN that should be only digits
      * @param actualMetrics - metrics for password to validate.
      * @return a list of password validation errors. An empty list means the password is OK.
      *
      * TODO: move to PasswordPolicy
      */
     public static List<PasswordValidationError> validatePasswordMetrics(
-            PasswordMetrics adminMetrics, int minComplexity, boolean isPin,
-            PasswordMetrics actualMetrics) {
+            PasswordMetrics adminMetrics, int minComplexity, PasswordMetrics actualMetrics) {
         final ComplexityBucket bucket = ComplexityBucket.forComplexity(minComplexity);
 
         // Make sure credential type is satisfactory.
@@ -561,7 +559,7 @@
             return Collections.emptyList(); // Nothing to check for pattern or none.
         }
 
-        if (isPin && actualMetrics.nonNumeric > 0) {
+        if (actualMetrics.credType == CREDENTIAL_TYPE_PIN && actualMetrics.nonNumeric > 0) {
             return Collections.singletonList(
                     new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
         }
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index 8866c76..de3eeee 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -54,6 +54,7 @@
     private boolean mIsImportantConversation;
     private String mNotificationKey;
     private CharSequence mNotificationContent;
+    private CharSequence mNotificationSender;
     private String mNotificationCategory;
     private Uri mNotificationDataUri;
     private int mMessagesCount;
@@ -73,6 +74,7 @@
         mIsImportantConversation = b.mIsImportantConversation;
         mNotificationKey = b.mNotificationKey;
         mNotificationContent = b.mNotificationContent;
+        mNotificationSender = b.mNotificationSender;
         mNotificationCategory = b.mNotificationCategory;
         mNotificationDataUri = b.mNotificationDataUri;
         mMessagesCount = b.mMessagesCount;
@@ -134,6 +136,10 @@
         return mNotificationContent;
     }
 
+    public CharSequence getNotificationSender() {
+        return mNotificationSender;
+    }
+
     public String getNotificationCategory() {
         return mNotificationCategory;
     }
@@ -179,6 +185,7 @@
         builder.setIsImportantConversation(mIsImportantConversation);
         builder.setNotificationKey(mNotificationKey);
         builder.setNotificationContent(mNotificationContent);
+        builder.setNotificationSender(mNotificationSender);
         builder.setNotificationCategory(mNotificationCategory);
         builder.setNotificationDataUri(mNotificationDataUri);
         builder.setMessagesCount(mMessagesCount);
@@ -201,6 +208,7 @@
         private boolean mIsImportantConversation;
         private String mNotificationKey;
         private CharSequence mNotificationContent;
+        private CharSequence mNotificationSender;
         private String mNotificationCategory;
         private Uri mNotificationDataUri;
         private int mMessagesCount;
@@ -316,6 +324,12 @@
             return this;
         }
 
+        /** Sets the associated notification's sender. */
+        public Builder setNotificationSender(CharSequence notificationSender) {
+            mNotificationSender = notificationSender;
+            return this;
+        }
+
         /** Sets the associated notification's category. */
         public Builder setNotificationCategory(String notificationCategory) {
             mNotificationCategory = notificationCategory;
@@ -371,6 +385,7 @@
         mIsImportantConversation = in.readBoolean();
         mNotificationKey = in.readString();
         mNotificationContent = in.readCharSequence();
+        mNotificationSender = in.readCharSequence();
         mNotificationCategory = in.readString();
         mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader());
         mMessagesCount = in.readInt();
@@ -398,6 +413,7 @@
         dest.writeBoolean(mIsImportantConversation);
         dest.writeString(mNotificationKey);
         dest.writeCharSequence(mNotificationContent);
+        dest.writeCharSequence(mNotificationSender);
         dest.writeString(mNotificationCategory);
         dest.writeParcelable(mNotificationDataUri, flags);
         dest.writeInt(mMessagesCount);
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index aa0bc3e..1fb7638 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -473,7 +473,7 @@
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()
                     && ((device == null) || isValidDevice(device))) {
-                return service.setActiveDevice(device);
+                return service.setActiveDevice(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -500,7 +500,7 @@
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
-                return service.getActiveDevice();
+                return service.getActiveDevice(mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return null;
@@ -560,7 +560,7 @@
                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                     return false;
                 }
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -590,7 +590,8 @@
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device));
+                return BluetoothAdapter.connectionPolicyToPriority(
+                        service.getPriority(device, mAttributionSource));
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.PRIORITY_OFF;
@@ -612,14 +613,18 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -664,7 +669,7 @@
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
-                service.setAvrcpAbsoluteVolume(volume);
+                service.setAvrcpAbsoluteVolume(volume, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
         } catch (RemoteException e) {
@@ -685,7 +690,7 @@
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return service.isA2dpPlaying(device);
+                return service.isA2dpPlaying(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -737,7 +742,7 @@
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
-                return service.getCodecStatus(device);
+                return service.getCodecStatus(device, mAttributionSource);
             }
             if (service == null) {
                 Log.w(TAG, "Proxy not attached to service");
@@ -772,7 +777,7 @@
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
-                service.setCodecConfigPreference(device, codecConfig);
+                service.setCodecConfigPreference(device, codecConfig, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return;
@@ -829,9 +834,9 @@
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
                 if (enable) {
-                    service.enableOptionalCodecs(device);
+                    service.enableOptionalCodecs(device, mAttributionSource);
                 } else {
-                    service.disableOptionalCodecs(device);
+                    service.disableOptionalCodecs(device, mAttributionSource);
                 }
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
@@ -860,7 +865,7 @@
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.supportsOptionalCodecs(device);
+                return service.supportsOptionalCodecs(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
@@ -888,7 +893,7 @@
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.getOptionalCodecsEnabled(device);
+                return service.getOptionalCodecsEnabled(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return OPTIONAL_CODECS_PREF_UNKNOWN;
@@ -924,7 +929,7 @@
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                service.setOptionalCodecsEnabled(device, value);
+                service.setOptionalCodecsEnabled(device, value, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return;
@@ -946,13 +951,17 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @Type int getDynamicBufferSupport() {
         if (VDBG) log("getDynamicBufferSupport()");
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
-                return service.getDynamicBufferSupport();
+                return service.getDynamicBufferSupport(mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return DYNAMIC_BUFFER_SUPPORT_NONE;
@@ -973,13 +982,17 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @Nullable BufferConstraints getBufferConstraints() {
         if (VDBG) log("getBufferConstraints()");
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
-                return service.getBufferConstraints();
+                return service.getBufferConstraints(mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return null;
@@ -999,14 +1012,18 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setBufferLengthMillis(@BluetoothCodecConfig.SourceCodecType int codec,
             int value) {
         if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")");
         try {
             final IBluetoothA2dp service = getService();
             if (service != null && isEnabled()) {
-                return service.setBufferLengthMillis(codec, value);
+                return service.setBufferLengthMillis(codec, value, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index 0158e3c..c0a2aa3 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -132,13 +132,17 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -179,7 +183,7 @@
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -203,7 +207,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -227,7 +231,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -250,7 +255,7 @@
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -279,7 +284,7 @@
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getAudioConfig(device);
+                return service.getAudioConfig(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return null;
@@ -338,7 +343,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -358,7 +363,11 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -376,13 +385,17 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -401,12 +414,16 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean isAudioPlaying(@NonNull BluetoothDevice device) {
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.isA2dpPlaying(device);
+                return service.isA2dpPlaying(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 18e6356..8afc557 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -981,7 +981,7 @@
         }
         String packageName = ActivityThread.currentPackageName();
         try {
-            return mManagerService.disableBle(packageName, mToken);
+            return mManagerService.disableBle(mAttributionSource, mToken);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1028,7 +1028,7 @@
         }
         String packageName = ActivityThread.currentPackageName();
         try {
-            return mManagerService.enableBle(packageName, mToken);
+            return mManagerService.enableBle(mAttributionSource, mToken);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1193,7 +1193,7 @@
             return true;
         }
         try {
-            return mManagerService.enable(ActivityThread.currentPackageName());
+            return mManagerService.enable(mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1226,7 +1226,7 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disable() {
         try {
-            return mManagerService.disable(ActivityThread.currentPackageName(), true);
+            return mManagerService.disable(mAttributionSource, true);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1246,7 +1246,7 @@
     public boolean disable(boolean persist) {
 
         try {
-            return mManagerService.disable(ActivityThread.currentPackageName(), persist);
+            return mManagerService.disable(mAttributionSource, persist);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1267,7 +1267,7 @@
     })
     public String getAddress() {
         try {
-            return mManagerService.getAddress();
+            return mManagerService.getAddress(mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1285,7 +1285,7 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public String getName() {
         try {
-            return mManagerService.getName();
+            return mManagerService.getName(mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1297,7 +1297,7 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     public int getNameLengthForAdvertise() {
         try {
-            return mService.getNameLengthForAdvertise();
+            return mService.getNameLengthForAdvertise(mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1316,7 +1316,8 @@
         try {
             mServiceLock.readLock().lock();
             if (mService != null && mService.factoryReset()
-                    && mManagerService != null && mManagerService.onFactoryReset()) {
+                    && mManagerService != null
+                    && mManagerService.onFactoryReset(mAttributionSource)) {
                 return true;
             }
             Log.e(TAG, "factoryReset(): Setting persist.bluetooth.factoryreset to retry later");
@@ -1910,7 +1911,7 @@
             mServiceLock.readLock().lock();
             if (mService != null) {
                 if (DBG) Log.d(TAG, "removeActiveDevice, profiles: " + profiles);
-                return mService.removeActiveDevice(profiles);
+                return mService.removeActiveDevice(profiles, mAttributionSource);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
@@ -1962,7 +1963,7 @@
                 if (DBG) {
                     Log.d(TAG, "setActiveDevice, device: " + device + ", profiles: " + profiles);
                 }
-                return mService.setActiveDevice(device, profiles);
+                return mService.setActiveDevice(device, profiles, mAttributionSource);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
@@ -1995,7 +1996,7 @@
         try {
             mServiceLock.readLock().lock();
             if (mService != null) {
-                return mService.connectAllEnabledProfiles(device);
+                return mService.connectAllEnabledProfiles(device, mAttributionSource);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
@@ -2027,7 +2028,7 @@
         try {
             mServiceLock.readLock().lock();
             if (mService != null) {
-                return mService.disconnectAllEnabledProfiles(device);
+                return mService.disconnectAllEnabledProfiles(device, mAttributionSource);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
@@ -3219,7 +3220,7 @@
             return true;
         }
         try {
-            return mManagerService.enableNoAutoConnect(ActivityThread.currentPackageName());
+            return mManagerService.enableNoAutoConnect(mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index be3d5ee..0b43e71 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -136,7 +136,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -159,7 +159,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -181,7 +182,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -205,7 +206,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                settings = service.getPlayerSettings(device);
+                settings = service.getPlayerSettings(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
                 return null;
@@ -226,7 +227,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                return service.setPlayerApplicationSetting(plAppSetting);
+                return service.setPlayerApplicationSetting(plAppSetting, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
                 return false;
@@ -249,7 +250,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                service.sendGroupNavigationCmd(device, keyCode, keyState);
+                service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource);
                 return;
             } catch (RemoteException e) {
                 Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index c700a10..98823b09 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1376,9 +1376,7 @@
             return false;
         }
         try {
-            return service.setRemoteAlias(this, alias,
-                    mAttributionSource.getPackageName(),
-                    mAttributionSource);
+            return service.setRemoteAlias(this, alias, mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 92f3f54..9dc2d8e 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -545,7 +545,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -608,7 +609,8 @@
             }
             try {
                 return service.setPriority(
-                        device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+                        device, BluetoothAdapter.priorityToConnectionPolicy(priority),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -647,7 +649,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -677,7 +679,8 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device));
+                return BluetoothAdapter.connectionPolicyToPriority(
+                        service.getPriority(device, mAttributionSource));
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.PRIORITY_OFF;
@@ -699,13 +702,17 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -729,7 +736,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.isNoiseReductionSupported(device);
+                return service.isNoiseReductionSupported(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -752,7 +759,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.isVoiceRecognitionSupported(device);
+                return service.isVoiceRecognitionSupported(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -791,7 +798,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.startVoiceRecognition(device);
+                return service.startVoiceRecognition(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -820,7 +827,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.stopVoiceRecognition(device);
+                return service.stopVoiceRecognition(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -843,7 +850,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.isAudioConnected(device);
+                return service.isAudioConnected(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -877,7 +884,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && !isDisabled()) {
             try {
-                return service.getAudioState(device);
+                return service.getAudioState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -905,7 +912,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                service.setAudioRouteAllowed(allowed);
+                service.setAudioRouteAllowed(allowed, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -928,7 +935,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.getAudioRouteAllowed();
+                return service.getAudioRouteAllowed(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -953,7 +960,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                service.setForceScoAudio(forced);
+                service.setForceScoAudio(forced, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -978,7 +985,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.isAudioOn();
+                return service.isAudioOn(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1013,7 +1020,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.connectAudio();
+                return service.connectAudio(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1042,7 +1049,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.disconnectAudio();
+                return service.disconnectAudio(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1086,7 +1093,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.startScoUsingVirtualVoiceCall();
+                return service.startScoUsingVirtualVoiceCall(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1121,7 +1128,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.stopScoUsingVirtualVoiceCall();
+                return service.stopScoUsingVirtualVoiceCall(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1151,7 +1158,8 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                service.phoneStateChanged(numActive, numHeld, callState, number, type, name);
+                service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1176,7 +1184,8 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                service.clccResponse(index, direction, status, mode, mpty, number, type);
+                service.clccResponse(index, direction, status, mode, mpty, number, type,
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1216,7 +1225,8 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendVendorSpecificResultCode(device, command, arg);
+                return service.sendVendorSpecificResultCode(device, command, arg,
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1260,7 +1270,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
             try {
-                return service.setActiveDevice(device);
+                return service.setActiveDevice(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1290,7 +1300,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.getActiveDevice();
+                return service.getActiveDevice(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1318,7 +1328,7 @@
         final IBluetoothHeadset service = mService;
         if (service != null && isEnabled()) {
             try {
-                return service.isInbandRingingEnabled();
+                return service.isInbandRingingEnabled(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index de0043f..0059cdb 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -483,7 +483,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -511,7 +511,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -536,7 +536,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -563,7 +563,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -588,7 +589,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -641,7 +642,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -689,7 +690,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -718,7 +719,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.startVoiceRecognition(device);
+                return service.startVoiceRecognition(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -745,7 +746,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendVendorAtCommand(device, vendorId, atCommand);
+                return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -773,7 +774,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.stopVoiceRecognition(device);
+                return service.stopVoiceRecognition(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -796,7 +797,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getCurrentCalls(device);
+                return service.getCurrentCalls(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -819,7 +820,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getCurrentAgEvents(device);
+                return service.getCurrentAgEvents(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -846,7 +847,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.acceptCall(device, flag);
+                return service.acceptCall(device, flag, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -870,7 +871,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.holdCall(device);
+                return service.holdCall(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -899,7 +900,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.rejectCall(device);
+                return service.rejectCall(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -932,7 +933,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.terminateCall(device, call);
+                return service.terminateCall(device, call, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -963,7 +964,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.enterPrivateMode(device, index);
+                return service.enterPrivateMode(device, index, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -993,7 +994,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.explicitCallTransfer(device);
+                return service.explicitCallTransfer(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1019,7 +1020,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.dial(device, number);
+                return service.dial(device, number, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1046,7 +1047,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendDTMF(device, code);
+                return service.sendDTMF(device, code, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1075,7 +1076,7 @@
                 getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getLastVoiceTagNumber(device);
+                return service.getLastVoiceTagNumber(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -1098,7 +1099,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                return service.getAudioState(device);
+                return service.getAudioState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1124,7 +1125,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                service.setAudioRouteAllowed(device, allowed);
+                service.setAudioRouteAllowed(device, allowed, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1149,7 +1150,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                return service.getAudioRouteAllowed(device);
+                return service.getAudioRouteAllowed(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1176,7 +1177,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                return service.connectAudio(device);
+                return service.connectAudio(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1203,7 +1204,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                return service.disconnectAudio(device);
+                return service.disconnectAudio(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1227,7 +1228,7 @@
                 getService();
         if (service != null && isEnabled()) {
             try {
-                return service.getCurrentAgFeatures(device);
+                return service.getCurrentAgFeatures(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 491937d..3ff2ebd 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -185,7 +185,7 @@
         final IBluetoothHearingAid service = getService();
         try {
             if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -217,13 +217,17 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHearingAid service = getService();
         try {
             if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -245,7 +249,7 @@
         try {
             if (service != null && isEnabled()) {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
@@ -268,7 +272,8 @@
         try {
             if (service != null && isEnabled()) {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
@@ -291,7 +296,7 @@
         try {
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -330,7 +335,7 @@
         try {
             if (service != null && isEnabled()
                     && ((device == null) || isValidDevice(device))) {
-                service.setActiveDevice(device);
+                service.setActiveDevice(device, mAttributionSource);
                 return true;
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
@@ -358,7 +363,7 @@
         final IBluetoothHearingAid service = getService();
         try {
             if (service != null && isEnabled()) {
-                return service.getActiveDevices();
+                return service.getActiveDevices(mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<>();
@@ -419,7 +424,7 @@
                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                     return false;
                 }
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -439,7 +444,11 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -457,7 +466,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         verifyDeviceNotNull(device, "getConnectionPolicy");
@@ -465,7 +478,7 @@
         try {
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -517,7 +530,7 @@
 
             if (!isEnabled()) return;
 
-            service.setVolume(volume);
+            service.setVolume(volume, mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
         }
@@ -534,7 +547,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public long getHiSyncId(@NonNull BluetoothDevice device) {
         if (VDBG) {
             log("getHiSyncId(" + device + ")");
@@ -549,7 +566,7 @@
 
             if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
 
-            return service.getHiSyncId(device);
+            return service.getHiSyncId(device, mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return HI_SYNC_ID_INVALID;
@@ -574,7 +591,7 @@
         try {
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return service.getDeviceSide(device);
+                return service.getDeviceSide(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return SIDE_LEFT;
@@ -602,7 +619,7 @@
         try {
             if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return service.getDeviceMode(device);
+                return service.getDeviceMode(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return MODE_MONAURAL;
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index 6abaa22..11e5711 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -450,7 +450,7 @@
         if (service != null) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -470,7 +470,8 @@
         if (service != null) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -489,7 +490,7 @@
         final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -549,7 +550,7 @@
         if (service != null) {
             try {
                 CallbackWrapper cbw = new CallbackWrapper(executor, callback);
-                result = service.registerApp(sdp, inQos, outQos, cbw);
+                result = service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -578,7 +579,7 @@
         final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
-                result = service.unregisterApp();
+                result = service.unregisterApp(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -605,7 +606,7 @@
         final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
-                result = service.sendReport(device, id, data);
+                result = service.sendReport(device, id, data, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -633,7 +634,7 @@
         final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
-                result = service.replyReport(device, type, id, data);
+                result = service.replyReport(device, type, id, data, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -659,7 +660,7 @@
         final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
-                result = service.reportError(device, error);
+                result = service.reportError(device, error, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -683,7 +684,7 @@
 
         if (service != null) {
             try {
-                return service.getUserAppName();
+                return service.getUserAppName(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -710,7 +711,7 @@
         final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
-                result = service.connect(device);
+                result = service.connect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -736,7 +737,7 @@
         final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
-                result = service.disconnect(device);
+                result = service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -781,7 +782,7 @@
                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                     return false;
                 }
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index c655422..0abe18c 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -284,13 +284,17 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -322,13 +326,17 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -353,7 +361,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -377,7 +385,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -404,7 +413,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -425,7 +434,11 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -444,7 +457,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -458,7 +475,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -478,7 +495,11 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -496,7 +517,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         if (device == null) {
@@ -505,7 +530,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -538,7 +563,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.virtualUnplug(device);
+                return service.virtualUnplug(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -565,7 +590,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getProtocolMode(device);
+                return service.getProtocolMode(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -590,7 +615,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.setProtocolMode(device, protocolMode);
+                return service.setProtocolMode(device, protocolMode, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -622,7 +647,8 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getReport(device, reportType, reportId, bufferSize);
+                return service.getReport(device, reportType, reportId, bufferSize,
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -649,7 +675,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.setReport(device, reportType, report);
+                return service.setReport(device, reportType, report, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -675,7 +701,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendData(device, report);
+                return service.sendData(device, report, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -700,7 +726,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getIdleTime(device);
+                return service.getIdleTime(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -726,7 +752,7 @@
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.setIdleTime(device, idleTime);
+                return service.setIdleTime(device, idleTime, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java
index a8ca9a8..51bfd04 100644
--- a/core/java/android/bluetooth/BluetoothLeAudio.java
+++ b/core/java/android/bluetooth/BluetoothLeAudio.java
@@ -166,7 +166,7 @@
         try {
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -206,7 +206,7 @@
         try {
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -228,7 +228,7 @@
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled()) {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
@@ -251,7 +251,8 @@
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled()) {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
@@ -274,7 +275,7 @@
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled()
                     && isValidDevice(device)) {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -312,7 +313,7 @@
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled()
                     && ((device == null) || isValidDevice(device))) {
-                service.setActiveDevice(device);
+                service.setActiveDevice(device, mAttributionSource);
                 return true;
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
@@ -338,7 +339,7 @@
         try {
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled()) {
-                return service.getActiveDevices();
+                return service.getActiveDevices(mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<>();
@@ -363,7 +364,7 @@
         try {
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled()) {
-                return service.getGroupId(device);
+                return service.getGroupId(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return GROUP_ID_INVALID;
@@ -401,7 +402,7 @@
                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                     return false;
                 }
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -430,7 +431,7 @@
             final IBluetoothLeAudio service = getService();
             if (service != null && mAdapter.isEnabled()
                     && isValidDevice(device)) {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 68bb60a..88505b5 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -144,7 +144,7 @@
         final IBluetoothMap service = getService();
         if (service != null) {
             try {
-                return service.getState();
+                return service.getState(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -170,7 +170,7 @@
         final IBluetoothMap service = getService();
         if (service != null) {
             try {
-                return service.getClient();
+                return service.getClient(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -195,7 +195,7 @@
         final IBluetoothMap service = getService();
         if (service != null) {
             try {
-                return service.isConnected(device);
+                return service.isConnected(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -234,7 +234,7 @@
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -286,7 +286,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -311,7 +311,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -335,7 +336,7 @@
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -394,7 +395,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -446,7 +447,7 @@
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 823967d..14804db 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -21,9 +21,8 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -224,7 +223,7 @@
         final IBluetoothMapClient service = getService();
         if (service != null) {
             try {
-                return service.isConnected(device);
+                return service.isConnected(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -251,7 +250,7 @@
         final IBluetoothMapClient service = getService();
         if (service != null) {
             try {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -280,7 +279,7 @@
         final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
@@ -304,7 +303,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<>();
@@ -329,7 +328,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<>();
@@ -353,7 +353,7 @@
         final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -411,7 +411,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -462,7 +462,7 @@
         final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -499,7 +499,7 @@
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.sendMessage(device, contacts.toArray(new Uri[contacts.size()]),
-                        message, sentIntent, deliveredIntent);
+                        message, sentIntent, deliveredIntent, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -533,7 +533,8 @@
         final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
+                return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -559,7 +560,7 @@
         final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getUnreadMessages(device);
+                return service.getUnreadMessages(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -582,7 +583,8 @@
         final IBluetoothMapClient service = getService();
         try {
             return (service != null && isEnabled() && isValidDevice(device))
-                && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
+                    && ((service.getSupportedFeatures(device, mAttributionSource)
+                            & UPLOADING_FEATURE_BITMASK) > 0);
         } catch (RemoteException e) {
             Log.e(TAG, e.getMessage());
         }
@@ -616,7 +618,7 @@
         if (service != null && isEnabled() && isValidDevice(device) && handle != null &&
             (status == READ || status == UNREAD || status == UNDELETED  || status == DELETED)) {
             try {
-                return service.setMessageStatus(device, handle, status);
+                return service.setMessageStatus(device, handle, status, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index c386538..90c94de 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -22,10 +22,10 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
-import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
@@ -254,7 +254,7 @@
         final IBluetoothPan service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -294,7 +294,7 @@
         final IBluetoothPan service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
@@ -333,7 +333,7 @@
                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                     return false;
                 }
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -360,7 +360,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -387,7 +387,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -403,13 +404,17 @@
      */
     @SystemApi
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getConnectionState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         final IBluetoothPan service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -438,7 +443,7 @@
         final IBluetoothPan service = getService();
         if (service != null && isEnabled()) {
             try {
-                service.setBluetoothTethering(value, pkgName, mContext.getAttributionTag());
+                service.setBluetoothTethering(value, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             }
@@ -459,7 +464,7 @@
         final IBluetoothPan service = getService();
         if (service != null && isEnabled()) {
             try {
-                return service.isTetheringOn();
+                return service.isTetheringOn(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             }
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 1a179c7..2600029 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -233,7 +233,7 @@
         }
         try {
             return BluetoothDevice.setAttributionSource(
-                    service.getConnectedDevices(), mAttributionSource);
+                    service.getConnectedDevices(mAttributionSource), mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, e.toString());
         }
@@ -247,13 +247,17 @@
      */
     @SystemApi
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
         log("getConnectionState: device=" + device);
         try {
             final IBluetoothPbap service = mService;
             if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             }
             if (service == null) {
                 Log.w(TAG, "Proxy not attached to service");
@@ -282,7 +286,8 @@
         }
         try {
             return BluetoothDevice.setAttributionSource(
-                    service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                    service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                    mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, e.toString());
         }
@@ -322,7 +327,7 @@
                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                     return false;
                 }
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             }
             if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
@@ -350,7 +355,7 @@
             return false;
         }
         try {
-            service.disconnect(device);
+            service.disconnect(device, mAttributionSource);
             return true;
         } catch (RemoteException e) {
             Log.e(TAG, e.toString());
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index d9c69f0..3ebd8fe 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -114,7 +114,11 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean connect(BluetoothDevice device) {
         if (DBG) {
             log("connect(" + device + ") for PBAP Client.");
@@ -122,7 +126,7 @@
         final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device);
+                return service.connect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -142,7 +146,11 @@
      *
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) {
             log("disconnect(" + device + ")" + new Exception());
@@ -150,7 +158,7 @@
         final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                service.disconnect(device);
+                service.disconnect(device, mAttributionSource);
                 return true;
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
@@ -180,7 +188,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -208,7 +216,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -235,7 +244,7 @@
         final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -270,7 +279,11 @@
      * @return true if priority is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -288,7 +301,11 @@
      * @return true if connectionPolicy is set, false on error
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) {
@@ -301,7 +318,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -323,7 +340,11 @@
      * @return priority of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -340,7 +361,11 @@
      * @return connection policy of the device
      * @hide
      */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) {
             log("getConnectionPolicy(" + device + ")");
@@ -348,7 +373,7 @@
         final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index 832538e..0631abd 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -157,7 +157,7 @@
         final IBluetoothSap service = getService();
         if (service != null) {
             try {
-                return service.getState();
+                return service.getState(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -182,7 +182,7 @@
         final IBluetoothSap service = getService();
         if (service != null) {
             try {
-                return service.getClient();
+                return service.getClient(mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -207,7 +207,7 @@
         final IBluetoothSap service = getService();
         if (service != null) {
             try {
-                return service.isConnected(device);
+                return service.isConnected(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -245,7 +245,7 @@
         final IBluetoothSap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
+                return service.disconnect(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -269,7 +269,7 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getConnectedDevices(), mAttributionSource);
+                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -293,7 +293,8 @@
         if (service != null && isEnabled()) {
             try {
                 return BluetoothDevice.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states), mAttributionSource);
+                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
@@ -316,7 +317,7 @@
         final IBluetoothSap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
+                return service.getConnectionState(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
@@ -374,7 +375,7 @@
                 return false;
             }
             try {
-                return service.setConnectionPolicy(device, connectionPolicy);
+                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -425,7 +426,7 @@
         final IBluetoothSap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device);
+                return service.getConnectionPolicy(device, mAttributionSource);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 2c155d58..7ab731f 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -31,10 +31,9 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.Immutable;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
 
@@ -70,10 +69,10 @@
  * This is supported to handle cases where you don't have access to the caller's attribution
  * source and you can directly use the {@link AttributionSource.Builder} APIs. However,
  * if the data flows through more than two apps (more than you access the data for the
- * caller - which you cannot know ahead of time) you need to have a handle to the {@link
- * AttributionSource} for the calling app's context in order to create an attribution context.
- * This means you either need to have an API for the other app to send you its attribution
- * source or use a platform API that pipes the callers attribution source.
+ * caller) you need to have a handle to the {@link AttributionSource} for the calling app's
+ * context in order to create an attribution context. This means you either need to have an
+ * API for the other app to send you its attribution source or use a platform API that pipes
+ * the callers attribution source.
  * <p>
  * You cannot forge an attribution chain without the participation of every app in the
  * attribution chain (aside of the special case mentioned above). To create an attribution
@@ -85,80 +84,11 @@
  * permission protected APIs since some app in the chain may not have the permission.
  */
 @Immutable
-// TODO: Codegen doesn't properly verify the class if the parcelling is inner class
-// TODO: Codegen doesn't allow overriding the constructor to change its visibility
-// TODO: Codegen applies method level annotations to argument vs the generated member (@SystemApi)
-// TODO: Codegen doesn't properly read/write IBinder members
-// TODO: Codegen doesn't properly handle Set arguments
-// TODO: Codegen requires @SystemApi annotations on fields which breaks
-//      android.signature.cts.api.AnnotationTest (need to update the test)
-// @DataClass(genEqualsHashCode = true, genConstructor = false, genBuilder = true)
 public final class AttributionSource implements Parcelable {
-    /**
-     * @hide
-     */
-    static class RenouncedPermissionsParcelling implements Parcelling<Set<String>> {
+    private final @NonNull AttributionSourceState mAttributionSourceState;
 
-        @Override
-        public void parcel(Set<String> item, Parcel dest, int parcelFlags) {
-            if (item == null) {
-                dest.writeInt(-1);
-            } else {
-                dest.writeInt(item.size());
-                for (String permission : item) {
-                    dest.writeString8(permission);
-                }
-            }
-        }
-
-        @Override
-        public Set<String> unparcel(Parcel source) {
-            final int size = source.readInt();
-            if (size < 0) {
-                return null;
-            }
-            final ArraySet<String> result = new ArraySet<>(size);
-            for (int i = 0; i < size; i++) {
-                result.add(source.readString8());
-            }
-            return result;
-        }
-    }
-
-    /**
-     * The UID that is accessing the permission protected data.
-     */
-    private final int mUid;
-
-    /**
-     * The package that is accessing the permission protected data.
-     */
-    private @Nullable String mPackageName = null;
-
-    /**
-     * The attribution tag of the app accessing the permission protected data.
-     */
-    private @Nullable String mAttributionTag = null;
-
-    /**
-     * Unique token for that source.
-     *
-     * @hide
-     */
-    private @Nullable IBinder mToken = null;
-
-    /**
-     * Permissions that should be considered revoked regardless if granted.
-     *
-     * @hide
-     */
-    @DataClass.ParcelWith(RenouncedPermissionsParcelling.class)
-    private @Nullable Set<String> mRenouncedPermissions = null;
-
-    /**
-     * The next app to receive the permission protected data.
-     */
-    private @Nullable AttributionSource mNext = null;
+    private @Nullable AttributionSource mNextCached;
+    private @Nullable Set<String> mRenouncedPermissionsCached;
 
     /** @hide */
     @TestApi
@@ -171,8 +101,7 @@
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, /*token*/ null,
-                /*renouncedPermissions*/ null, next);
+        this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next);
     }
 
     /** @hide */
@@ -180,8 +109,8 @@
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
             @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, /*token*/ null,
-                renouncedPermissions, next);
+        this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null)
+                ? renouncedPermissions.toArray(new String[0]) : null, next);
     }
 
     /** @hide */
@@ -191,16 +120,49 @@
                 /*token*/ null, /*renouncedPermissions*/ null, next);
     }
 
+    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+            @Nullable IBinder token, @Nullable String[] renouncedPermissions,
+            @Nullable AttributionSource next) {
+        mAttributionSourceState = new AttributionSourceState();
+        mAttributionSourceState.uid = uid;
+        mAttributionSourceState.packageName = packageName;
+        mAttributionSourceState.attributionTag = attributionTag;
+        mAttributionSourceState.token = token;
+        mAttributionSourceState.renouncedPermissions = renouncedPermissions;
+        mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
+                {next.mAttributionSourceState} : null;
+    }
+
+    AttributionSource(@NonNull Parcel in) {
+        this(AttributionSourceState.CREATOR.createFromParcel(in));
+    }
+
+    /** @hide */
+    public AttributionSource(@NonNull AttributionSourceState attributionSourceState) {
+        mAttributionSourceState = attributionSourceState;
+    }
+
     /** @hide */
     public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
-        return new AttributionSource(mUid, mPackageName, mAttributionTag,  mToken,
-                mRenouncedPermissions, next);
+        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+                getToken(), mAttributionSourceState.renouncedPermissions, next);
     }
 
     /** @hide */
     public AttributionSource withToken(@Nullable IBinder token) {
-        return new AttributionSource(mUid, mPackageName, mAttributionTag, token,
-                mRenouncedPermissions, mNext);
+        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+                token, mAttributionSourceState.renouncedPermissions, getNext());
+    }
+
+    /** @hide */
+    public AttributionSource withPackageName(@Nullable String packageName) {
+        return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(),
+                mAttributionSourceState.renouncedPermissions, getNext());
+    }
+
+    /** @hide */
+    public @NonNull AttributionSourceState asState() {
+        return mAttributionSourceState;
     }
 
     /**
@@ -213,10 +175,9 @@
      * from the caller.
      */
     public void enforceCallingUid() {
-        final int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
-            throw new SecurityException("Calling uid: " + callingUid
-                    + " doesn't match source uid: " + mUid);
+        if (!checkCallingUid()) {
+            throw new SecurityException("Calling uid: " + Binder.getCallingUid()
+                    + " doesn't match source uid: " + mAttributionSourceState.uid);
         }
         // No need to check package as app ops manager does it already.
     }
@@ -231,7 +192,8 @@
      */
     public boolean checkCallingUid() {
         final int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
+        if (callingUid != Process.SYSTEM_UID
+                && callingUid != mAttributionSourceState.uid) {
             return false;
         }
         // No need to check package as app ops manager does it already.
@@ -242,11 +204,12 @@
     public String toString() {
         if (Build.IS_DEBUGGABLE) {
             return "AttributionSource { " +
-                    "uid = " + mUid + ", " +
-                    "packageName = " + mPackageName + ", " +
-                    "attributionTag = " + mAttributionTag + ", " +
-                    "token = " + mToken + ", " +
-                    "next = " + mNext +
+                    "uid = " + mAttributionSourceState.uid + ", " +
+                    "packageName = " + mAttributionSourceState.packageName + ", " +
+                    "attributionTag = " + mAttributionSourceState.attributionTag + ", " +
+                    "token = " + mAttributionSourceState.token + ", " +
+                    "next = " + (mAttributionSourceState.next != null
+                            ? mAttributionSourceState.next[0]: null) +
                     " }";
         }
         return super.toString();
@@ -258,8 +221,8 @@
      * @hide
      */
     public int getNextUid() {
-        if (mNext != null) {
-            return mNext.getUid();
+        if (mAttributionSourceState.next != null) {
+            return mAttributionSourceState.next[0].uid;
         }
         return Process.INVALID_UID;
     }
@@ -270,8 +233,8 @@
      * @hide
      */
     public @Nullable String getNextPackageName() {
-        if (mNext != null) {
-            return mNext.getPackageName();
+        if (mAttributionSourceState.next != null) {
+            return mAttributionSourceState.next[0].packageName;
         }
         return null;
     }
@@ -283,8 +246,8 @@
      * @hide
      */
     public @Nullable String getNextAttributionTag() {
-        if (mNext != null) {
-            return mNext.getAttributionTag();
+        if (mAttributionSourceState.next != null) {
+            return mAttributionSourceState.next[0].attributionTag;
         }
         return null;
     }
@@ -297,8 +260,9 @@
      * @return Whether this is a trusted source.
      */
     public boolean isTrusted(@NonNull Context context) {
-        return mToken != null && context.getSystemService(PermissionManager.class)
-                .isRegisteredAttributionSource(this);
+        return mAttributionSourceState.token != null
+                && context.getSystemService(PermissionManager.class)
+                        .isRegisteredAttributionSource(this);
     }
 
     /**
@@ -310,71 +274,36 @@
     @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
     @NonNull
     public Set<String> getRenouncedPermissions() {
-        return CollectionUtils.emptyIfNull(mRenouncedPermissions);
-    }
-
-    @DataClass.Suppress({"setUid", "setToken"})
-    static class BaseBuilder {}
-
-
-
-
-
-
-    // Code below generated by codegen v1.0.22.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/AttributionSource.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    /* package-private */ AttributionSource(
-            int uid,
-            @Nullable String packageName,
-            @Nullable String attributionTag,
-            @Nullable IBinder token,
-            @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @Nullable Set<String> renouncedPermissions,
-            @Nullable AttributionSource next) {
-        this.mUid = uid;
-        this.mPackageName = packageName;
-        this.mAttributionTag = attributionTag;
-        this.mToken = token;
-        this.mRenouncedPermissions = renouncedPermissions;
-        com.android.internal.util.AnnotationValidations.validate(
-                SystemApi.class, null, mRenouncedPermissions);
-        com.android.internal.util.AnnotationValidations.validate(
-                RequiresPermission.class, null, mRenouncedPermissions,
-                "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
-        this.mNext = next;
-
-        // onConstructed(); // You can define this method to get a callback
+        if (mRenouncedPermissionsCached == null) {
+            if (mAttributionSourceState.renouncedPermissions != null) {
+                mRenouncedPermissionsCached = new ArraySet<>(
+                        mAttributionSourceState.renouncedPermissions);
+            } else {
+                mRenouncedPermissionsCached = Collections.emptySet();
+            }
+        }
+        return mRenouncedPermissionsCached;
     }
 
     /**
      * The UID that is accessing the permission protected data.
      */
     public int getUid() {
-        return mUid;
+        return mAttributionSourceState.uid;
     }
 
     /**
      * The package that is accessing the permission protected data.
      */
     public @Nullable String getPackageName() {
-        return mPackageName;
+        return mAttributionSourceState.packageName;
     }
 
     /**
      * The attribution tag of the app accessing the permission protected data.
      */
     public @Nullable String getAttributionTag() {
-        return mAttributionTag;
+        return mAttributionSourceState.attributionTag;
     }
 
     /**
@@ -383,113 +312,56 @@
      * @hide
      */
     public @Nullable IBinder getToken() {
-        return mToken;
+        return mAttributionSourceState.token;
     }
 
     /**
      * The next app to receive the permission protected data.
      */
     public @Nullable AttributionSource getNext() {
-        return mNext;
+        if (mNextCached == null && mAttributionSourceState.next != null) {
+            mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
+        }
+        return mNextCached;
     }
 
     @Override
     public boolean equals(@Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(AttributionSource other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
         AttributionSource that = (AttributionSource) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mUid == that.mUid
-                && Objects.equals(mPackageName, that.mPackageName)
-                && Objects.equals(mAttributionTag, that.mAttributionTag)
-                && Objects.equals(mToken, that.mToken)
-                && Objects.equals(mRenouncedPermissions, that.mRenouncedPermissions)
-                && Objects.equals(mNext, that.mNext);
+        return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+                && Objects.equals(mAttributionSourceState.packageName,
+                        that.mAttributionSourceState.packageName)
+                && Objects.equals(mAttributionSourceState.attributionTag,
+                        that.mAttributionSourceState.attributionTag)
+                && Objects.equals(mAttributionSourceState.token,
+                        that.mAttributionSourceState.token)
+                && Arrays.equals(mAttributionSourceState.renouncedPermissions,
+                        that.mAttributionSourceState.renouncedPermissions)
+                && Objects.equals(getNext(), that.getNext());
     }
 
     @Override
     public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
         int _hash = 1;
-        _hash = 31 * _hash + mUid;
-        _hash = 31 * _hash + Objects.hashCode(mPackageName);
-        _hash = 31 * _hash + Objects.hashCode(mAttributionTag);
-        _hash = 31 * _hash + Objects.hashCode(mToken);
-        _hash = 31 * _hash + Objects.hashCode(mRenouncedPermissions);
-        _hash = 31 * _hash + Objects.hashCode(mNext);
+        _hash = 31 * _hash + mAttributionSourceState.uid;
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName);
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag);
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token);
+        _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions);
+        _hash = 31 * _hash + Objects.hashCode(getNext());
         return _hash;
     }
 
-    static Parcelling<Set<String>> sParcellingForRenouncedPermissions =
-            Parcelling.Cache.get(
-                    RenouncedPermissionsParcelling.class);
-    static {
-        if (sParcellingForRenouncedPermissions == null) {
-            sParcellingForRenouncedPermissions = Parcelling.Cache.put(
-                    new RenouncedPermissionsParcelling());
-        }
-    }
-
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mPackageName != null) flg |= 0x2;
-        if (mAttributionTag != null) flg |= 0x4;
-        if (mToken != null) flg |= 0x8;
-        if (mRenouncedPermissions != null) flg |= 0x10;
-        if (mNext != null) flg |= 0x20;
-        dest.writeByte(flg);
-        dest.writeInt(mUid);
-        if (mPackageName != null) dest.writeString(mPackageName);
-        if (mAttributionTag != null) dest.writeString(mAttributionTag);
-        if (mToken != null) dest.writeStrongBinder(mToken);
-        sParcellingForRenouncedPermissions.parcel(mRenouncedPermissions, dest, flags);
-        if (mNext != null) dest.writeTypedObject(mNext, flags);
+        mAttributionSourceState.writeToParcel(dest, flags);
     }
 
     @Override
     public int describeContents() { return 0; }
 
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    /* package-private */ AttributionSource(@NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        int uid = in.readInt();
-        String packageName = (flg & 0x2) == 0 ? null : in.readString();
-        String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
-        IBinder token = (flg & 0x8) == 0 ? null : in.readStrongBinder();
-        Set<String> renouncedPermissions = sParcellingForRenouncedPermissions.unparcel(in);
-        AttributionSource next = (flg & 0x20) == 0 ? null : (AttributionSource) in.readTypedObject(AttributionSource.CREATOR);
-
-        this.mUid = uid;
-        this.mPackageName = packageName;
-        this.mAttributionTag = attributionTag;
-        this.mToken = token;
-        this.mRenouncedPermissions = renouncedPermissions;
-        com.android.internal.util.AnnotationValidations.validate(
-                SystemApi.class, null, mRenouncedPermissions);
-        com.android.internal.util.AnnotationValidations.validate(
-                RequiresPermission.class, null, mRenouncedPermissions,
-                "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
-        this.mNext = next;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
     public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR
             = new Parcelable.Creator<AttributionSource>() {
         @Override
@@ -506,15 +378,9 @@
     /**
      * A builder for {@link AttributionSource}
      */
-    @SuppressWarnings("WeakerAccess")
-    public static final class Builder extends BaseBuilder {
-
-        private int mUid;
-        private @Nullable String mPackageName;
-        private @Nullable String mAttributionTag;
-        private @Nullable IBinder mToken;
-        private @Nullable Set<String> mRenouncedPermissions;
-        private @Nullable AttributionSource mNext;
+    public static final class Builder {
+        private @NonNull final AttributionSourceState mAttributionSourceState =
+                new AttributionSourceState();
 
         private long mBuilderFieldsSet = 0L;
 
@@ -524,9 +390,8 @@
          * @param uid
          *   The UID that is accessing the permission protected data.
          */
-        public Builder(
-                int uid) {
-            mUid = uid;
+        public Builder(int uid) {
+            mAttributionSourceState.uid = uid;
         }
 
         /**
@@ -535,7 +400,7 @@
         public @NonNull Builder setPackageName(@Nullable String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2;
-            mPackageName = value;
+            mAttributionSourceState.packageName = value;
             return this;
         }
 
@@ -545,7 +410,7 @@
         public @NonNull Builder setAttributionTag(@Nullable String value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x4;
-            mAttributionTag = value;
+            mAttributionSourceState.attributionTag = value;
             return this;
         }
 
@@ -578,7 +443,8 @@
         public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x10;
-            mRenouncedPermissions = value;
+            mAttributionSourceState.renouncedPermissions = (value != null)
+                    ? value.toArray(new String[0]) : null;
             return this;
         }
 
@@ -588,7 +454,8 @@
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
-            mNext = value;
+            mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
+                    {value.mAttributionSourceState} : null;
             return this;
         }
 
@@ -598,28 +465,21 @@
             mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x2) == 0) {
-                mPackageName = null;
+                mAttributionSourceState.packageName = null;
             }
             if ((mBuilderFieldsSet & 0x4) == 0) {
-                mAttributionTag = null;
+                mAttributionSourceState.attributionTag = null;
             }
             if ((mBuilderFieldsSet & 0x8) == 0) {
-                mToken = null;
+                mAttributionSourceState.token = null;
             }
             if ((mBuilderFieldsSet & 0x10) == 0) {
-                mRenouncedPermissions = null;
+                mAttributionSourceState.renouncedPermissions = null;
             }
             if ((mBuilderFieldsSet & 0x20) == 0) {
-                mNext = null;
+                mAttributionSourceState.next = null;
             }
-            AttributionSource o = new AttributionSource(
-                    mUid,
-                    mPackageName,
-                    mAttributionTag,
-                    mToken,
-                    mRenouncedPermissions,
-                    mNext);
-            return o;
+            return new AttributionSource(mAttributionSourceState);
         }
 
         private void checkNotUsed() {
@@ -629,9 +489,4 @@
             }
         }
     }
-
-
-    //@formatter:on
-    // End of generated code
-
 }
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 5089f30..66e0883 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,21 +16,19 @@
 
 package android.content;
 
-import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.Process;
-import android.util.Slog;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.permission.IPermissionChecker;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * This class provides permission check APIs that verify both the
@@ -72,34 +70,44 @@
  * @hide
  */
 public final class PermissionChecker {
-    private static final String LOG_TAG = PermissionChecker.class.getName();
+    /**
+     * The permission is granted.
+     *
+     * @hide
+     */
+    public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
 
-    private static final String PLATFORM_PACKAGE_NAME = "android";
-
-    /** The permission is granted. */
-    public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED;
-
-    /** Only for runtime permissions, its returned when the runtime permission
-     * is granted, but the corresponding app op is denied. */
-    public static final int PERMISSION_SOFT_DENIED = AppOpsManager.MODE_IGNORED;
-
-    /** Returned when:
+    /**
+     * The permission is denied. Applicable only to runtime and app op permissions.
+     *
+     * <p>Returned when:
      * <ul>
-     * <li>For non app op permissions, returned when the permission is denied.</li>
-     * <li>For app op permissions, returned when the app op is denied or app op is
-     * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li>
+     *   <li>the runtime permission is granted, but the corresponding app op is denied
+     *       for runtime permissions.</li>
+     *   <li>the app ops is ignored for app op permissions.</li>
      * </ul>
      *
+     * @hide
      */
-    public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED;
+    public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
+
+    /**
+     * The permission is denied.
+     *
+     * <p>Returned when:
+     * <ul>
+     *   <li>the permission is denied for non app op permissions.</li>
+     *   <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT}
+     *   and permission is denied.</li>
+     * </ul>
+     *
+     * @hide
+     */
+    public static final int PERMISSION_HARD_DENIED =  IPermissionChecker.PERMISSION_HARD_DENIED;
 
     /** Constant when the PID for which we check permissions is unknown. */
     public static final int PID_UNKNOWN = -1;
 
-    // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
-    private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
-            = new ConcurrentHashMap<>();
-
     /** @hide */
     @IntDef({PERMISSION_GRANTED,
             PERMISSION_SOFT_DENIED,
@@ -107,6 +115,8 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionResult {}
 
+    private static volatile IPermissionChecker sService;
+
     private PermissionChecker() {
         /* do nothing */
     }
@@ -232,7 +242,7 @@
     public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context,
             @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+        return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
                 message, false /*startDataDelivery*/, /*fromDatasource*/ true);
     }
 
@@ -307,21 +317,23 @@
     public static int checkPermissionForDataDelivery(@NonNull Context context,
             @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean startDataDelivery) {
-        return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+        return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
                 message, startDataDelivery, /*fromDatasource*/ false);
     }
 
     private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
-            @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+            @NonNull String permission, @NonNull AttributionSource attributionSource,
             @Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
         // If the check failed in the middle of the chain, finish any started op.
-        final int result = checkPermissionCommon(context, permission, attributionSource,
-                message, true /*forDataDelivery*/, startDataDelivery, fromDatasource);
-        if (startDataDelivery && result != PERMISSION_GRANTED) {
-            finishDataDelivery(context, AppOpsManager.permissionToOp(permission),
-                    attributionSource);
+        try {
+            final int result = getPermissionCheckerService().checkPermission(permission,
+                    attributionSource.asState(), message, true /*forDataDelivery*/,
+                    startDataDelivery, fromDatasource);
+            return result;
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
-        return result;
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -356,9 +368,14 @@
     public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
             @NonNull String permission, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkPermissionCommon(context, permission, attributionSource,
-                message, true /*forDataDelivery*/, /*startDataDelivery*/ true,
-                /*fromDatasource*/ false);
+        try {
+            return getPermissionCheckerService().checkPermission(permission,
+                    attributionSource.asState(), message, true /*forDataDelivery*/,
+                    /*startDataDelivery*/ true, /*fromDatasource*/ false);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -390,13 +407,14 @@
     public static int startOpForDataDelivery(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        final int result = checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
-                message, true /*forDataDelivery*/, true /*startDataDelivery*/);
-        // It is important to finish any started op if some step in the attribution chain failed.
-        if (result != PERMISSION_GRANTED) {
-            finishDataDelivery(context, opName, attributionSource);
+        try {
+            return getPermissionCheckerService().checkOp(
+                    AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+                    true /*forDataDelivery*/, true /*startDataDelivery*/);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
-        return result;
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -412,15 +430,10 @@
      */
     public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
             @NonNull AttributionSource attributionSource) {
-        if (op == null || attributionSource.getPackageName() == null) {
-            return;
-        }
-
-        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-        appOpsManager.finishProxyOp(op, attributionSource);
-
-        if (attributionSource.getNext() != null) {
-            finishDataDelivery(context, op, attributionSource.getNext());
+        try {
+            getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
         }
     }
 
@@ -456,8 +469,14 @@
     public static int checkOpForPreflight(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
-                message,  false /*forDataDelivery*/, false /*startDataDelivery*/);
+        try {
+            return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
+                    attributionSource.asState(), message, false /*forDataDelivery*/,
+                    false /*startDataDelivery*/);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -489,8 +508,14 @@
     public static int checkOpForDataDelivery(@NonNull Context context,
             @NonNull String opName, @NonNull AttributionSource attributionSource,
             @Nullable String message) {
-        return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
-                message,  true /*forDataDelivery*/, false /*startDataDelivery*/);
+        try {
+            return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
+                    attributionSource.asState(), message, true /*forDataDelivery*/,
+                    false /*startDataDelivery*/);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -561,9 +586,14 @@
     @PermissionResult
     public static int checkPermissionForPreflight(@NonNull Context context,
             @NonNull String permission, @NonNull AttributionSource attributionSource) {
-        return checkPermissionCommon(context, permission, attributionSource,
-                null /*message*/, false /*forDataDelivery*/, /*startDataDelivery*/ false,
-                /*fromDatasource*/ false);
+        try {
+            return getPermissionCheckerService().checkPermission(permission,
+                    attributionSource.asState(), null /*message*/, false /*forDataDelivery*/,
+                    /*startDataDelivery*/ false, /*fromDatasource*/ false);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return PERMISSION_HARD_DENIED;
     }
 
     /**
@@ -798,356 +828,12 @@
                 Binder.getCallingUid(), packageName);
     }
 
-    @PermissionResult
-    private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
-            @NonNull AttributionSource attributionSource,
-            @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
-            boolean fromDatasource) {
-        PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
-
-        if (permissionInfo == null) {
-            try {
-                permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
-                if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
-                    // Double addition due to concurrency is fine - the backing store is concurrent.
-                    sPlatformPermissions.put(permission, permissionInfo);
-                }
-            } catch (PackageManager.NameNotFoundException ignored) {
-                return PERMISSION_HARD_DENIED;
-            }
+    private static @NonNull IPermissionChecker getPermissionCheckerService() {
+        // Race is fine, we may end up looking up the same instance twice, no big deal.
+        if (sService == null) {
+            final IBinder service = ServiceManager.getService("permission_checker");
+            sService = IPermissionChecker.Stub.asInterface(service);
         }
-
-        if (permissionInfo.isAppOp()) {
-            return checkAppOpPermission(context, permission, attributionSource, message,
-                    forDataDelivery, fromDatasource);
-        }
-        if (permissionInfo.isRuntime()) {
-            return checkRuntimePermission(context, permission, attributionSource, message,
-                    forDataDelivery, startDataDelivery, fromDatasource);
-        }
-
-        if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
-                attributionSource.getRenouncedPermissions())) {
-            return PERMISSION_HARD_DENIED;
-        }
-
-        if (attributionSource.getNext() != null) {
-            return checkPermissionCommon(context, permission,
-                    attributionSource.getNext(), message, forDataDelivery,
-                    startDataDelivery, /*fromDatasource*/ false);
-        }
-
-        return PERMISSION_GRANTED;
-    }
-
-    @PermissionResult
-    private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean fromDatasource) {
-        final int op = AppOpsManager.permissionToOpCode(permission);
-        if (op < 0) {
-            Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!");
-            return PERMISSION_HARD_DENIED;
-        }
-
-        AttributionSource current = attributionSource;
-        AttributionSource next = null;
-
-        while (true) {
-            final boolean skipCurrentChecks = (fromDatasource || next != null);
-
-            next = current.getNext();
-
-            // If the call is from a datasource we need to vet only the chain before it. This
-            // way we can avoid the datasource creating an attribution context for every call.
-            if (!(fromDatasource && current == attributionSource)
-                    && next != null && !current.isTrusted(context)) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            // The access is for oneself if this is the single receiver of data
-            // after the data source or if this is the single attribution source
-            // in the chain if not from a datasource.
-            final boolean singleReceiverFromDatasource = (fromDatasource
-                    && current == attributionSource && next != null && next.getNext() == null);
-            final boolean selfAccess = singleReceiverFromDatasource || next == null;
-
-            final int opMode = performOpTransaction(context, op, current, message,
-                    forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
-                    selfAccess, singleReceiverFromDatasource);
-
-            switch (opMode) {
-                case AppOpsManager.MODE_IGNORED:
-                case AppOpsManager.MODE_ERRORED: {
-                    return PERMISSION_HARD_DENIED;
-                }
-                case AppOpsManager.MODE_DEFAULT: {
-                    if (!skipCurrentChecks && !checkPermission(context, permission,
-                            attributionSource.getUid(), attributionSource
-                                    .getRenouncedPermissions())) {
-                        return PERMISSION_HARD_DENIED;
-                    }
-                    if (next != null && !checkPermission(context, permission,
-                            next.getUid(), next.getRenouncedPermissions())) {
-                        return PERMISSION_HARD_DENIED;
-                    }
-                }
-            }
-
-            if (next == null || next.getNext() == null) {
-                return PERMISSION_GRANTED;
-            }
-
-            current = next;
-        }
-    }
-
-    private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
-        // Now let's check the identity chain...
-        final int op = AppOpsManager.permissionToOpCode(permission);
-
-        AttributionSource current = attributionSource;
-        AttributionSource next = null;
-
-        while (true) {
-            final boolean skipCurrentChecks = (fromDatasource || next != null);
-            next = current.getNext();
-
-            // If the call is from a datasource we need to vet only the chain before it. This
-            // way we can avoid the datasource creating an attribution context for every call.
-            if (!(fromDatasource && current == attributionSource)
-                    && next != null && !current.isTrusted(context)) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            // If we already checked the permission for this one, skip the work
-            if (!skipCurrentChecks && !checkPermission(context, permission,
-                    current.getUid(), current.getRenouncedPermissions())) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            if (next != null && !checkPermission(context, permission,
-                    next.getUid(), next.getRenouncedPermissions())) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            if (op < 0) {
-                // Bg location is one-off runtime modifier permission and has no app op
-                if (sPlatformPermissions.contains(permission)
-                        && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
-                    Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
-                            + " with no app op defined!");
-                }
-                if (next == null) {
-                    return PERMISSION_GRANTED;
-                }
-                current = next;
-                continue;
-            }
-
-            // The access is for oneself if this is the single receiver of data
-            // after the data source or if this is the single attribution source
-            // in the chain if not from a datasource.
-            final boolean singleReceiverFromDatasource = (fromDatasource
-                    && current == attributionSource && next != null && next.getNext() == null);
-            final boolean selfAccess = singleReceiverFromDatasource || next == null;
-
-            final int opMode = performOpTransaction(context, op, current, message,
-                    forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                    singleReceiverFromDatasource);
-
-            switch (opMode) {
-                case AppOpsManager.MODE_ERRORED: {
-                    return PERMISSION_HARD_DENIED;
-                }
-                case AppOpsManager.MODE_IGNORED: {
-                    return PERMISSION_SOFT_DENIED;
-                }
-            }
-
-            if (next == null || next.getNext() == null) {
-                return PERMISSION_GRANTED;
-            }
-
-            current = next;
-        }
-    }
-
-    private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
-            int uid, @NonNull Set<String> renouncedPermissions) {
-        final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
-                uid) == PackageManager.PERMISSION_GRANTED;
-        if (permissionGranted && renouncedPermissions.contains(permission)
-                && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
-                        /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
-            return false;
-        }
-        return permissionGranted;
-    }
-
-    private static int checkOp(@NonNull Context context, @NonNull int op,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean startDataDelivery) {
-        if (op < 0 || attributionSource.getPackageName() == null) {
-            return PERMISSION_HARD_DENIED;
-        }
-
-        AttributionSource current = attributionSource;
-        AttributionSource next = null;
-
-        while (true) {
-            final boolean skipCurrentChecks = (next != null);
-            next = current.getNext();
-
-            // If the call is from a datasource we need to vet only the chain before it. This
-            // way we can avoid the datasource creating an attribution context for every call.
-            if (next != null && !current.isTrusted(context)) {
-                return PERMISSION_HARD_DENIED;
-            }
-
-            // The access is for oneself if this is the single attribution source in the chain.
-            final boolean selfAccess = (next == null);
-
-            final int opMode = performOpTransaction(context, op, current, message,
-                    forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
-                    /*fromDatasource*/ false);
-
-            switch (opMode) {
-                case AppOpsManager.MODE_ERRORED: {
-                    return PERMISSION_HARD_DENIED;
-                }
-                case AppOpsManager.MODE_IGNORED: {
-                    return PERMISSION_SOFT_DENIED;
-                }
-            }
-
-            if (next == null || next.getNext() == null) {
-                return PERMISSION_GRANTED;
-            }
-
-            current = next;
-        }
-    }
-
-    private static int performOpTransaction(@NonNull Context context, int op,
-            @NonNull AttributionSource attributionSource, @Nullable String message,
-            boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
-            boolean selfAccess, boolean singleReceiverFromDatasource) {
-        // We cannot perform app ops transactions without a package name. In all relevant
-        // places we pass the package name but just in case there is a bug somewhere we
-        // do a best effort to resolve the package from the UID (pick first without a loss
-        // of generality - they are in the same security sandbox).
-        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-        final AttributionSource accessorSource = (!singleReceiverFromDatasource)
-                ? attributionSource : attributionSource.getNext();
-        if (!forDataDelivery) {
-            final String resolvedAccessorPackageName = resolvePackageName(context, accessorSource);
-            if (resolvedAccessorPackageName == null) {
-                return AppOpsManager.MODE_ERRORED;
-            }
-            final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
-                    accessorSource.getUid(), resolvedAccessorPackageName);
-            final AttributionSource next = accessorSource.getNext();
-            if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
-                final String resolvedNextPackageName = resolvePackageName(context, next);
-                if (resolvedNextPackageName == null) {
-                    return AppOpsManager.MODE_ERRORED;
-                }
-                return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
-                        resolvedNextPackageName);
-            }
-            return opMode;
-        } else if (startDataDelivery) {
-            final AttributionSource resolvedAttributionSource = resolveAttributionSource(
-                    context, accessorSource);
-            if (resolvedAttributionSource.getPackageName() == null) {
-                return AppOpsManager.MODE_ERRORED;
-            }
-            if (selfAccess) {
-                // If the datasource is not in a trusted platform component then in would not
-                // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
-                // an app is exposing runtime permission protected data but cannot blame others
-                // in a trusted way which would not properly show in permission usage UIs.
-                // As a fallback we note a proxy op that blames the app and the datasource.
-                try {
-                    return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
-                            resolvedAttributionSource.getPackageName(),
-                            /*startIfModeDefault*/ false,
-                            resolvedAttributionSource.getAttributionTag(),
-                            message);
-                } catch (SecurityException e) {
-                    Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
-                            + " platform defined runtime permission "
-                            + AppOpsManager.opToPermission(op) + " while not having "
-                            + Manifest.permission.UPDATE_APP_OPS_STATS);
-                    return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
-                            skipProxyOperation);
-                }
-            } else {
-                return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
-                        skipProxyOperation);
-            }
-        } else {
-            final AttributionSource resolvedAttributionSource = resolveAttributionSource(
-                    context, accessorSource);
-            if (resolvedAttributionSource.getPackageName() == null) {
-                return AppOpsManager.MODE_ERRORED;
-            }
-            if (selfAccess) {
-                // If the datasource is not in a trusted platform component then in would not
-                // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
-                // an app is exposing runtime permission protected data but cannot blame others
-                // in a trusted way which would not properly show in permission usage UIs.
-                // As a fallback we note a proxy op that blames the app and the datasource.
-                try {
-                    return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
-                            resolvedAttributionSource.getPackageName(),
-                            resolvedAttributionSource.getAttributionTag(),
-                            message);
-                } catch (SecurityException e) {
-                    Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
-                            + " platform defined runtime permission "
-                            + AppOpsManager.opToPermission(op) + " while not having "
-                            + Manifest.permission.UPDATE_APP_OPS_STATS);
-                    return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
-                            skipProxyOperation);
-                }
-            } else {
-                return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
-                        skipProxyOperation);
-            }
-        }
-    }
-
-    private static @Nullable String resolvePackageName(@NonNull Context context,
-            @NonNull AttributionSource attributionSource) {
-        if (attributionSource.getPackageName() != null) {
-            return attributionSource.getPackageName();
-        }
-        final String[] packageNames = context.getPackageManager().getPackagesForUid(
-                attributionSource.getUid());
-        if (packageNames != null) {
-            // This is best effort if the caller doesn't pass a package. The security
-            // sandbox is UID, therefore we pick an arbitrary package.
-            return packageNames[0];
-        }
-        // Last resort to handle special UIDs like root, etc.
-        return AppOpsManager.resolvePackageName(attributionSource.getUid(),
-                attributionSource.getPackageName());
-    }
-
-    private static @NonNull AttributionSource resolveAttributionSource(
-            @NonNull Context context, @NonNull AttributionSource attributionSource) {
-        if (attributionSource.getPackageName() != null) {
-            return attributionSource;
-        }
-        return new AttributionSource(attributionSource.getUid(),
-                resolvePackageName(context, attributionSource),
-                attributionSource.getAttributionTag(),
-                attributionSource.getToken(),
-                attributionSource.getRenouncedPermissions(),
-                attributionSource.getNext());
+        return sService;
     }
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7fe2a41..5e72325 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -796,6 +796,10 @@
 
     void setMimeGroup(String packageName, String group, in List<String> mimeTypes);
 
+    String getSplashScreenTheme(String packageName, int userId);
+
+    void setSplashScreenTheme(String packageName, String themeName, int userId);
+
     List<String> getMimeGroup(String packageName, String group);
 
     boolean isAutoRevokeWhitelisted(String packageName);
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 55a6ab7..84317b3 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -81,6 +81,7 @@
     public int installReason;
     public @PackageManager.UninstallReason int uninstallReason;
     public String harmfulAppWarning;
+    public String splashScreenTheme;
 
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
@@ -130,6 +131,7 @@
         if (o.componentLabelIconOverrideMap != null) {
             this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap);
         }
+        splashScreenTheme = o.splashScreenTheme;
     }
 
     @Nullable
@@ -242,6 +244,7 @@
         return componentLabelIconOverrideMap.get(componentName);
     }
 
+
     /**
      * Test if this package is installed.
      */
@@ -479,7 +482,11 @@
         }
         if (harmfulAppWarning == null && oldState.harmfulAppWarning != null
                 || (harmfulAppWarning != null
-                        && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
+                && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
+            return false;
+        }
+
+        if (!Objects.equals(splashScreenTheme, oldState.splashScreenTheme)) {
             return false;
         }
         return true;
@@ -505,6 +512,7 @@
         hashCode = 31 * hashCode + Objects.hashCode(disabledComponents);
         hashCode = 31 * hashCode + Objects.hashCode(enabledComponents);
         hashCode = 31 * hashCode + Objects.hashCode(harmfulAppWarning);
+        hashCode = 31 * hashCode + Objects.hashCode(splashScreenTheme);
         return hashCode;
     }
 
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index fd10c57..80b5078 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -73,6 +73,9 @@
  * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, Class)} for supported
  * repeating request output sizes.</p>
  *
+ * <p>The extension characteristics for a given device are expected to remain static under
+ * normal operating conditions.</p>
+ *
  * @see CameraManager#getCameraExtensionCharacteristics(String)
  */
 public final class CameraExtensionCharacteristics {
@@ -585,7 +588,7 @@
      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
      *                                  {@link ImageFormat#YUV_420_888}; or unsupported extension.
      */
-    public @Nullable Range<Long> getEstimatedCaptureLatencyRange(@Extension int extension,
+    public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
             @NonNull Size captureOutputSize, @ImageFormat.Format int format) {
         switch (format) {
             case ImageFormat.YUV_420_888:
diff --git a/core/java/android/hardware/display/BrightnessInfo.aidl b/core/java/android/hardware/display/BrightnessInfo.aidl
new file mode 100644
index 0000000..5da55c3
--- /dev/null
+++ b/core/java/android/hardware/display/BrightnessInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.hardware.display;
+
+parcelable BrightnessInfo;
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
new file mode 100644
index 0000000..4289860
--- /dev/null
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -0,0 +1,109 @@
+/*
+ * 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 android.hardware.display;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Data about the current brightness state.
+ * {@see android.view.Display.getBrightnessInfo()}
+ *
+ * @hide
+ */
+public final class BrightnessInfo implements Parcelable {
+
+    @IntDef(prefix = {"HIGH_BRIGHTNESS_MODE_"}, value = {
+            HIGH_BRIGHTNESS_MODE_OFF,
+            HIGH_BRIGHTNESS_MODE_SUNLIGHT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HighBrightnessMode {}
+
+    /**
+     * High brightness mode is OFF. The high brightness range is not currently accessible to the
+     * user.
+     */
+    public static final int HIGH_BRIGHTNESS_MODE_OFF = 0;
+
+    /**
+     * High brightness mode is ON due to high ambient light (sunlight). The high brightness range is
+     * currently accessible to the user.
+     */
+    public static final int HIGH_BRIGHTNESS_MODE_SUNLIGHT = 1;
+
+    /** Brightness */
+    public final float brightness;
+
+    /** Current minimum supported brightness. */
+    public final float brightnessMinimum;
+
+    /** Current maximum supported brightness. */
+    public final float brightnessMaximum;
+
+    /**
+     * Current state of high brightness mode.
+     * Can be any of HIGH_BRIGHTNESS_MODE_* values.
+     */
+    public final int highBrightnessMode;
+
+    public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
+            @HighBrightnessMode int highBrightnessMode) {
+        this.brightness = brightness;
+        this.brightnessMinimum = brightnessMinimum;
+        this.brightnessMaximum = brightnessMaximum;
+        this.highBrightnessMode = highBrightnessMode;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeFloat(brightness);
+        dest.writeFloat(brightnessMinimum);
+        dest.writeFloat(brightnessMaximum);
+        dest.writeInt(highBrightnessMode);
+    }
+
+    public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
+            new Creator<BrightnessInfo>() {
+                @Override
+                public BrightnessInfo createFromParcel(Parcel source) {
+                    return new BrightnessInfo(source);
+                }
+
+                @Override
+                public BrightnessInfo[] newArray(int size) {
+                    return new BrightnessInfo[size];
+                }
+            };
+
+    private BrightnessInfo(Parcel source) {
+        brightness = source.readFloat();
+        brightnessMinimum = source.readFloat();
+        brightnessMaximum = source.readFloat();
+        highBrightnessMode = source.readInt();
+    }
+
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 6c2d140..de32adb1 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -418,6 +418,7 @@
             EVENT_FLAG_DISPLAY_ADDED,
             EVENT_FLAG_DISPLAY_CHANGED,
             EVENT_FLAG_DISPLAY_REMOVED,
+            EVENT_FLAG_DISPLAY_BRIGHTNESS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventsMask {}
@@ -449,6 +450,17 @@
      */
     public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2;
 
+    /**
+     * Event flag to register for a display's brightness changes. This notification is sent
+     * through the {@link DisplayListener#onDisplayChanged} callback method. New brightness
+     * values can be retrieved via {@link android.view.Display#getBrightnessInfo()}.
+     *
+     * @see #registerDisplayListener(DisplayListener, Handler, long)
+     *
+     * @hide
+     */
+    public static final long EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 3;
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
@@ -583,6 +595,7 @@
      * @see #EVENT_FLAG_DISPLAY_ADDED
      * @see #EVENT_FLAG_DISPLAY_CHANGED
      * @see #EVENT_FLAG_DISPLAY_REMOVED
+     * @see #EVENT_FLAG_DISPLAY_BRIGHTNESS
      * @see #registerDisplayListener(DisplayListener, Handler)
      * @see #unregisterDisplayListener
      *
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 983a43a..df51734 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -79,6 +79,7 @@
             EVENT_DISPLAY_ADDED,
             EVENT_DISPLAY_CHANGED,
             EVENT_DISPLAY_REMOVED,
+            EVENT_DISPLAY_BRIGHTNESS_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DisplayEvent {}
@@ -86,6 +87,7 @@
     public static final int EVENT_DISPLAY_ADDED = 1;
     public static final int EVENT_DISPLAY_CHANGED = 2;
     public static final int EVENT_DISPLAY_REMOVED = 3;
+    public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4;
 
     @UnsupportedAppUsage
     private static DisplayManagerGlobal sInstance;
@@ -665,6 +667,17 @@
     }
 
     /**
+     * Retrieves Brightness Info for the specified display.
+     */
+    public BrightnessInfo getBrightnessInfo(int displayId) {
+        try {
+            return mDm.getBrightnessInfo(displayId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the preferred wide gamut color space for all displays.
      * The wide gamut color space is returned from composition pipeline
      * based on hardware capability.
@@ -934,6 +947,11 @@
                         }
                     }
                     break;
+                case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
+                    if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) {
+                        mListener.onDisplayChanged(msg.arg1);
+                    }
+                    break;
                 case EVENT_DISPLAY_REMOVED:
                     if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) {
                         mListener.onDisplayRemoved(msg.arg1);
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 5ca4e0c..2303353 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -19,6 +19,7 @@
 import android.content.pm.ParceledListSlice;
 import android.graphics.Point;
 import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -143,6 +144,9 @@
     // Get the minimum brightness curve.
     Curve getMinimumBrightnessCurve();
 
+    // Get Brightness Information for the specified display.
+    BrightnessInfo getBrightnessInfo(int displayId);
+
     // Gets the id of the preferred wide gamut color space for all displays.
     // The wide gamut color space is returned from composition pipeline
     // based on hardware capability.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 13e2700..5f87899 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -133,11 +133,11 @@
         }
 
         @Override
-        public void onFeatureGet(boolean success, int feature, boolean value) {
+        public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = success;
-            args.argi1 = feature;
-            args.arg2 = value;
+            args.arg2 = features;
+            args.arg3 = featureState;
             mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
         }
 
@@ -1088,7 +1088,7 @@
      * @hide
      */
     public abstract static class GetFeatureCallback {
-        public abstract void onCompleted(boolean success, int feature, boolean value);
+        public abstract void onCompleted(boolean success, int[] features, boolean[] featureState);
     }
 
     /**
@@ -1179,8 +1179,8 @@
                 case MSG_GET_FEATURE_COMPLETED:
                     SomeArgs args = (SomeArgs) msg.obj;
                     sendGetFeatureCompleted((boolean) args.arg1 /* success */,
-                            args.argi1 /* feature */,
-                            (boolean) args.arg2 /* value */);
+                            (int[]) args.arg2 /* features */,
+                            (boolean[]) args.arg3 /* featureState */);
                     args.recycle();
                     break;
                 case MSG_CHALLENGE_GENERATED:
@@ -1216,11 +1216,11 @@
         mSetFeatureCallback.onCompleted(success, feature);
     }
 
-    private void sendGetFeatureCompleted(boolean success, int feature, boolean value) {
+    private void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
         if (mGetFeatureCallback == null) {
             return;
         }
-        mGetFeatureCallback.onCompleted(success, feature, value);
+        mGetFeatureCallback.onCompleted(success, features, featureState);
     }
 
     private void sendChallengeGenerated(int sensorId, long challenge) {
diff --git a/core/java/android/hardware/face/FaceServiceReceiver.java b/core/java/android/hardware/face/FaceServiceReceiver.java
index f0f975d..9e62ca5 100644
--- a/core/java/android/hardware/face/FaceServiceReceiver.java
+++ b/core/java/android/hardware/face/FaceServiceReceiver.java
@@ -66,7 +66,8 @@
     }
 
     @Override
-    public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException {
+    public void onFeatureGet(boolean success, int[] features, boolean[] featureState)
+                throws RemoteException {
 
     }
 
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 2ef1430..0ccb395 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -32,7 +32,7 @@
     void onError(int error, int vendorCode);
     void onRemoved(in Face face, int remaining);
     void onFeatureSet(boolean success, int feature);
-    void onFeatureGet(boolean success, int feature, boolean value);
+    void onFeatureGet(boolean success, in int[] features, in boolean[] featureState);
     void onChallengeGenerated(int sensorId, long challenge);
     void onChallengeInterrupted(int sensorId);
     void onChallengeInterruptFinished(int sensorId);
diff --git a/core/java/android/hardware/face/OWNERS b/core/java/android/hardware/face/OWNERS
index be10df1..0b4d9d9 100644
--- a/core/java/android/hardware/face/OWNERS
+++ b/core/java/android/hardware/face/OWNERS
@@ -1,7 +1,3 @@
 # Bug component: 879035
 
-curtislb@google.com
-ilyamaty@google.com
-jaggies@google.com
-joshmccloskey@google.com
-kchyn@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/java/android/hardware/fingerprint/OWNERS b/core/java/android/hardware/fingerprint/OWNERS
index e55b8c56..5c93672 100644
--- a/core/java/android/hardware/fingerprint/OWNERS
+++ b/core/java/android/hardware/fingerprint/OWNERS
@@ -1,8 +1,3 @@
 # Bug component: 114777
 
-curtislb@google.com
-ilyamaty@google.com
-jaggies@google.com
-joshmccloskey@google.com
-kchyn@google.com
-
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/java/android/hardware/iris/OWNERS b/core/java/android/hardware/iris/OWNERS
index 33527f8..0b4d9d9 100644
--- a/core/java/android/hardware/iris/OWNERS
+++ b/core/java/android/hardware/iris/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 879035
 
-jaggies@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b3502f3..95962c8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -554,9 +554,12 @@
     /**
      * Query the estimated durations of the given primitives.
      *
-     * The returned array will be the same length as the query array and the value at a given index
-     * will contain the duration in milliseconds of the effect at the same index in the querying
-     * array.
+     * <p>The returned array will be the same length as the query array and the value at a given
+     * index will contain the duration in milliseconds of the effect at the same index in the
+     * querying array.
+     *
+     * <p>The duration will be positive for primitives that are supported and zero for the
+     * unsupported ones, in correspondence with {@link #arePrimitivesSupported(int...)}.
      *
      * @param primitiveIds Which primitives to query for.
      * @return The duration of each primitive, with zeroes for primitives that are not supported.
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 17c90d6..64d5d9c 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -20,6 +20,7 @@
 
 import android.Manifest;
 import android.annotation.CheckResult;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,6 +32,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.ChangeId;
@@ -63,6 +65,8 @@
 import com.android.internal.annotations.Immutable;
 import com.android.internal.util.CollectionUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f8991ce..55cdb26 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -83,6 +83,7 @@
 import android.util.Log;
 import android.util.MemoryIntArray;
 import android.view.Display;
+import android.view.Window;
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.annotations.GuardedBy;
@@ -13372,6 +13373,19 @@
         public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale";
 
         /**
+         * Setting to disable cross-window blurs. This includes window blur behind, (see
+         *  {@link LayoutParams#setBlurBehindRadius}) and window background blur (see
+         *  {@link Window#setBackgroundBlurRadius}).
+         *
+         * The value is a boolean (1 or 0).
+         * @hide
+         */
+        @TestApi
+        @Readable
+        @SuppressLint("NoSettingsProvider")
+        public static final String DISABLE_WINDOW_BLURS = "disable_window_blurs";
+
+        /**
          * Scaling factor for activity transition animations.
          *
          * The value is a float. Setting to 0.0f will disable window animations.
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 3c53e8f..bacc6ec 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -349,7 +349,7 @@
         private final HotwordDetectedResult mHotwordDetectedResult;
         private final ParcelFileDescriptor mAudioStream;
 
-        private EventPayload(boolean triggerAvailable, boolean captureAvailable,
+        EventPayload(boolean triggerAvailable, boolean captureAvailable,
                 AudioFormat audioFormat, int captureSession, byte[] data) {
             this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, null,
                     null);
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index f5d796f..e50de1c 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.media.MediaSyncEvent;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 
@@ -53,9 +54,17 @@
     }
 
     /**
+     * A {@code MediaSyncEvent} that allows the {@link HotwordDetector} to recapture the audio
+     * that contains the hotword trigger. This must be obtained using
+     * {@link android.media.AudioRecord#shareAudioHistory(String, long)}.
+     * <p>
+     * This can be {@code null} if reprocessing the hotword trigger isn't required.
+     */
+    @Nullable
+    private MediaSyncEvent mMediaSyncEvent = null;
+
+    /**
      * Byte offset in the audio stream when the trigger event happened.
-     *
-     * <p>If unset, the most recent bytes in the audio stream will be used.
      */
     private final int mByteOffset;
     private static int defaultByteOffset() {
@@ -84,7 +93,7 @@
 
     /**
      * Returns the maximum values of {@link #getScore} and {@link #getPersonalizedScore}.
-     *
+     * <p>
      * The float value should be calculated as {@code getScore() / getMaxScore()}.
      */
     public static int getMaxScore() {
@@ -159,6 +168,7 @@
     @DataClass.Generated.Member
     /* package-private */ HotwordDetectedResult(
             @HotwordDetector.HotwordConfidenceLevelValue int confidenceLevel,
+            @Nullable MediaSyncEvent mediaSyncEvent,
             int byteOffset,
             int score,
             int personalizedScore,
@@ -167,6 +177,7 @@
         this.mConfidenceLevel = confidenceLevel;
         com.android.internal.util.AnnotationValidations.validate(
                 HotwordDetector.HotwordConfidenceLevelValue.class, null, mConfidenceLevel);
+        this.mMediaSyncEvent = mediaSyncEvent;
         this.mByteOffset = byteOffset;
         this.mScore = score;
         this.mPersonalizedScore = personalizedScore;
@@ -187,9 +198,19 @@
     }
 
     /**
+     * A {@code MediaSyncEvent} that allows the {@link HotwordDetector} to recapture the audio
+     * that contains the hotword trigger. This must be obtained using
+     * {@link android.media.AudioRecord#shareAudioHistory(String, long)}.
+     * <p>
+     * This can be {@code null} if reprocessing the hotword trigger isn't required.
+     */
+    @DataClass.Generated.Member
+    public @Nullable MediaSyncEvent getMediaSyncEvent() {
+        return mMediaSyncEvent;
+    }
+
+    /**
      * Byte offset in the audio stream when the trigger event happened.
-     *
-     * <p>If unset, the most recent bytes in the audio stream will be used.
      */
     @DataClass.Generated.Member
     public int getByteOffset() {
@@ -237,6 +258,9 @@
      *
      * <p>The use of this method is discouraged, and support for it will be removed in future
      * versions of Android.
+     *
+     * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
+     * that can be used to communicate with other processes.
      */
     @DataClass.Generated.Member
     public @NonNull PersistableBundle getExtras() {
@@ -251,6 +275,7 @@
 
         return "HotwordDetectedResult { " +
                 "confidenceLevel = " + mConfidenceLevel + ", " +
+                "mediaSyncEvent = " + mMediaSyncEvent + ", " +
                 "byteOffset = " + mByteOffset + ", " +
                 "score = " + mScore + ", " +
                 "personalizedScore = " + mPersonalizedScore + ", " +
@@ -273,6 +298,7 @@
         //noinspection PointlessBooleanExpression
         return true
                 && mConfidenceLevel == that.mConfidenceLevel
+                && java.util.Objects.equals(mMediaSyncEvent, that.mMediaSyncEvent)
                 && mByteOffset == that.mByteOffset
                 && mScore == that.mScore
                 && mPersonalizedScore == that.mPersonalizedScore
@@ -288,6 +314,7 @@
 
         int _hash = 1;
         _hash = 31 * _hash + mConfidenceLevel;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mMediaSyncEvent);
         _hash = 31 * _hash + mByteOffset;
         _hash = 31 * _hash + mScore;
         _hash = 31 * _hash + mPersonalizedScore;
@@ -302,7 +329,11 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
+        byte flg = 0;
+        if (mMediaSyncEvent != null) flg |= 0x2;
+        dest.writeByte(flg);
         dest.writeInt(mConfidenceLevel);
+        if (mMediaSyncEvent != null) dest.writeTypedObject(mMediaSyncEvent, flags);
         dest.writeInt(mByteOffset);
         dest.writeInt(mScore);
         dest.writeInt(mPersonalizedScore);
@@ -321,7 +352,9 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
+        byte flg = in.readByte();
         int confidenceLevel = in.readInt();
+        MediaSyncEvent mediaSyncEvent = (flg & 0x2) == 0 ? null : (MediaSyncEvent) in.readTypedObject(MediaSyncEvent.CREATOR);
         int byteOffset = in.readInt();
         int score = in.readInt();
         int personalizedScore = in.readInt();
@@ -331,6 +364,7 @@
         this.mConfidenceLevel = confidenceLevel;
         com.android.internal.util.AnnotationValidations.validate(
                 HotwordDetector.HotwordConfidenceLevelValue.class, null, mConfidenceLevel);
+        this.mMediaSyncEvent = mediaSyncEvent;
         this.mByteOffset = byteOffset;
         this.mScore = score;
         this.mPersonalizedScore = personalizedScore;
@@ -364,6 +398,7 @@
     public static final class Builder {
 
         private @HotwordDetector.HotwordConfidenceLevelValue int mConfidenceLevel;
+        private @Nullable MediaSyncEvent mMediaSyncEvent;
         private int mByteOffset;
         private int mScore;
         private int mPersonalizedScore;
@@ -387,14 +422,27 @@
         }
 
         /**
+         * A {@code MediaSyncEvent} that allows the {@link HotwordDetector} to recapture the audio
+         * that contains the hotword trigger. This must be obtained using
+         * {@link android.media.AudioRecord#shareAudioHistory(String, long)}.
+         * <p>
+         * This can be {@code null} if reprocessing the hotword trigger isn't required.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setMediaSyncEvent(@NonNull MediaSyncEvent value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mMediaSyncEvent = value;
+            return this;
+        }
+
+        /**
          * Byte offset in the audio stream when the trigger event happened.
-         *
-         * <p>If unset, the most recent bytes in the audio stream will be used.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setByteOffset(int value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
+            mBuilderFieldsSet |= 0x4;
             mByteOffset = value;
             return this;
         }
@@ -407,7 +455,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setScore(int value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
+            mBuilderFieldsSet |= 0x8;
             mScore = value;
             return this;
         }
@@ -420,7 +468,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setPersonalizedScore(int value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
+            mBuilderFieldsSet |= 0x10;
             mPersonalizedScore = value;
             return this;
         }
@@ -433,7 +481,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setHotwordPhraseId(int value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
+            mBuilderFieldsSet |= 0x20;
             mHotwordPhraseId = value;
             return this;
         }
@@ -449,11 +497,14 @@
          *
          * <p>The use of this method is discouraged, and support for it will be removed in future
          * versions of Android.
+         *
+         * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
+         * that can be used to communicate with other processes.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setExtras(@NonNull PersistableBundle value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
+            mBuilderFieldsSet |= 0x40;
             mExtras = value;
             return this;
         }
@@ -461,28 +512,32 @@
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull HotwordDetectedResult build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x40; // Mark builder used
+            mBuilderFieldsSet |= 0x80; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mConfidenceLevel = defaultConfidenceLevel();
             }
             if ((mBuilderFieldsSet & 0x2) == 0) {
-                mByteOffset = defaultByteOffset();
+                mMediaSyncEvent = null;
             }
             if ((mBuilderFieldsSet & 0x4) == 0) {
-                mScore = defaultScore();
+                mByteOffset = defaultByteOffset();
             }
             if ((mBuilderFieldsSet & 0x8) == 0) {
-                mPersonalizedScore = defaultPersonalizedScore();
+                mScore = defaultScore();
             }
             if ((mBuilderFieldsSet & 0x10) == 0) {
-                mHotwordPhraseId = defaultHotwordPhraseId();
+                mPersonalizedScore = defaultPersonalizedScore();
             }
             if ((mBuilderFieldsSet & 0x20) == 0) {
+                mHotwordPhraseId = defaultHotwordPhraseId();
+            }
+            if ((mBuilderFieldsSet & 0x40) == 0) {
                 mExtras = defaultExtras();
             }
             HotwordDetectedResult o = new HotwordDetectedResult(
                     mConfidenceLevel,
+                    mMediaSyncEvent,
                     mByteOffset,
                     mScore,
                     mPersonalizedScore,
@@ -492,7 +547,7 @@
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x40) != 0) {
+            if ((mBuilderFieldsSet & 0x80) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -500,10 +555,10 @@
     }
 
     @DataClass.Generated(
-            time = 1616965644404L,
+            time = 1619059352684L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int BYTE_OFFSET_UNSET\nprivate final @android.service.voice.HotwordDetector.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate final  int mByteOffset\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultByteOffset()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int BYTE_OFFSET_UNSET\nprivate final @android.service.voice.HotwordDetector.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate final  int mByteOffset\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultByteOffset()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index ea854e8..7e0117d 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -30,6 +30,7 @@
 import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.Intent;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.os.Bundle;
 import android.os.Handler;
@@ -133,7 +134,7 @@
     private final IHotwordDetectionService mInterface = new IHotwordDetectionService.Stub() {
         @Override
         public void detectFromDspSource(
-                ParcelFileDescriptor audioStream,
+                SoundTrigger.KeyphraseRecognitionEvent event,
                 AudioFormat audioFormat,
                 long timeoutMillis,
                 IDspHotwordDetectionCallback callback)
@@ -143,8 +144,9 @@
             }
             mHandler.sendMessage(obtainMessage(HotwordDetectionService::onDetect,
                     HotwordDetectionService.this,
-                    audioStream,
-                    audioFormat,
+                    new AlwaysOnHotwordDetector.EventPayload(
+                            event.triggerInData, event.captureAvailable,
+                            event.captureFormat, event.captureSession, event.data),
                     timeoutMillis,
                     new Callback(callback)));
         }
@@ -178,8 +180,6 @@
                     mHandler.sendMessage(obtainMessage(
                             HotwordDetectionService::onDetect,
                             HotwordDetectionService.this,
-                            audioStream,
-                            audioFormat,
                             new Callback(callback)));
                     break;
                 case AUDIO_SOURCE_EXTERNAL:
@@ -246,13 +246,42 @@
      *                      the application fails to abide by the timeout, system will close the
      *                      microphone and cancel the operation.
      * @param callback The callback to use for responding to the detection request.
+     * @deprecated Implement
+     * {@link #onDetect(AlwaysOnHotwordDetector.EventPayload, long, Callback)} instead.
+     *
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public void onDetect(
+            @NonNull ParcelFileDescriptor audioStream,
+            @NonNull AudioFormat audioFormat,
+            @DurationMillisLong long timeoutMillis,
+            @NonNull Callback callback) {
+        // TODO: Add a helpful error message.
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Called when the device hardware (such as a DSP) detected the hotword, to request second stage
+     * validation before handing over the audio to the {@link AlwaysOnHotwordDetector}.
+     * <p>
+     * After {@code callback} is invoked or {@code timeoutMillis} has passed, and invokes the
+     * appropriate {@link AlwaysOnHotwordDetector.Callback callback}.
+     *
+     * @param eventPayload Payload data for the hardware detection event. This may contain the
+     *                     trigger audio, if requested when calling
+     *                     {@link AlwaysOnHotwordDetector#startRecognition(int)}.
+     * @param timeoutMillis Timeout in milliseconds for the operation to invoke the callback. If
+     *                      the application fails to abide by the timeout, system will close the
+     *                      microphone and cancel the operation.
+     * @param callback The callback to use for responding to the detection request.
      *
      * @hide
      */
     @SystemApi
     public void onDetect(
-            @NonNull ParcelFileDescriptor audioStream,
-            @NonNull AudioFormat audioFormat,
+            @NonNull AlwaysOnHotwordDetector.EventPayload eventPayload,
             @DurationMillisLong long timeoutMillis,
             @NonNull Callback callback) {
         // TODO: Add a helpful error message.
@@ -305,7 +334,9 @@
      * @param audioFormat Format of the supplied audio
      * @param callback The callback to use for responding to the detection request.
      * {@link Callback#onRejected(HotwordRejectedResult) callback.onRejected} cannot be used here.
+     * @deprecated Implement {@link #onDetect(Callback)} instead.
      */
+    @Deprecated
     public void onDetect(
             @NonNull ParcelFileDescriptor audioStream,
             @NonNull AudioFormat audioFormat,
@@ -316,6 +347,22 @@
 
     /**
      * Called when the {@link VoiceInteractionService} requests that this service
+     * {@link HotwordDetector#startRecognition() start} hotword recognition on audio coming directly
+     * from the device microphone.
+     * <p>
+     * On successful detection of a hotword, call
+     * {@link Callback#onDetected(HotwordDetectedResult)}.
+     *
+     * @param callback The callback to use for responding to the detection request.
+     * {@link Callback#onRejected(HotwordRejectedResult) callback.onRejected} cannot be used here.
+     */
+    public void onDetect(@NonNull Callback callback) {
+        // TODO: Add a helpful error message.
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Called when the {@link VoiceInteractionService} requests that this service
      * {@link HotwordDetector#startRecognition(ParcelFileDescriptor, AudioFormat,
      * PersistableBundle)} run} hotword recognition on audio coming from an external connected
      * microphone.
diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/IHotwordDetectionService.aidl
index 2ffe787..7ba0098 100644
--- a/core/java/android/service/voice/IHotwordDetectionService.aidl
+++ b/core/java/android/service/voice/IHotwordDetectionService.aidl
@@ -16,6 +16,8 @@
 
 package android.service.voice;
 
+import android.content.ContentCaptureOptions;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
@@ -23,7 +25,6 @@
 import android.os.SharedMemory;
 import android.service.voice.IDspHotwordDetectionCallback;
 import android.view.contentcapture.IContentCaptureManager;
-import android.content.ContentCaptureOptions;
 
 /**
  * Provide the interface to communicate with hotword detection service.
@@ -32,7 +33,7 @@
  */
 oneway interface IHotwordDetectionService {
     void detectFromDspSource(
-        in ParcelFileDescriptor audioStream,
+        in SoundTrigger.KeyphraseRecognitionEvent event,
         in AudioFormat audioFormat,
         long timeoutMillis,
         in IDspHotwordDetectionCallback callback);
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 2a25227..b5c838b 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -420,9 +420,13 @@
      *
      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
      * AlwaysOnHotwordDetector.Callback)
+     * @deprecated Use
+     * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)}
+     * instead.
      *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
     @NonNull
@@ -445,6 +449,58 @@
     }
 
     /**
+     * Creates a {@link HotwordDetector} and initializes the application's
+     * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}.
+     *
+     * <p>To be able to call this, you need to set android:hotwordDetectionService in the
+     * android.voice_interaction metadata file to a valid hotword detection service, and set
+     * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise,
+     * this throws an {@link IllegalStateException}.
+     *
+     * <p>This instance must be retained and used by the client.
+     * Calling this a second time invalidates the previously created hotword detector
+     * which can no longer be used to manage recognition.
+     *
+     * <p>Using this has a noticeable impact on battery, since the microphone is kept open
+     * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On
+     * devices where hardware filtering is available (such as through a DSP), it's highly
+     * recommended to use {@link #createAlwaysOnHotwordDetector} instead.
+     *
+     * @param options Application configuration data to be provided to the
+     * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
+     * other contents that can be used to communicate with other processes.
+     * @param sharedMemory The unrestricted data blob to be provided to the
+     * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the
+     * sandboxed process.
+     * @param callback The callback to notify of detection events.
+     * @return A hotword detector for the given audio format.
+     *
+     * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+     * AlwaysOnHotwordDetector.Callback)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
+    @NonNull
+    public final HotwordDetector createHotwordDetector(
+            @Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory,
+            @NonNull HotwordDetector.Callback callback) {
+        if (mSystemService == null) {
+            throw new IllegalStateException("Not available until onReady() is called");
+        }
+        synchronized (mLock) {
+            // Allow only one concurrent recognition via the APIs.
+            safelyShutdownHotwordDetector();
+            mSoftwareHotwordDetector =
+                    new SoftwareHotwordDetector(
+                            mSystemService, null, options, sharedMemory, callback);
+        }
+        return mSoftwareHotwordDetector;
+    }
+
+    /**
      * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the
      * pre-bundled system voice models.
      * @hide
diff --git a/core/java/android/uwb/AdapterStateListener.java b/core/java/android/uwb/AdapterStateListener.java
index b990095..91847f7 100644
--- a/core/java/android/uwb/AdapterStateListener.java
+++ b/core/java/android/uwb/AdapterStateListener.java
@@ -68,8 +68,7 @@
                     mIsRegistered = true;
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failed to register adapter state callback");
-                    executor.execute(() -> callback.onStateChanged(mAdapterState,
-                            AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN));
+                    throw e.rethrowFromSystemServer();
                 }
             } else {
                 sendCurrentState(callback);
@@ -95,6 +94,7 @@
                     mAdapter.unregisterAdapterStateCallbacks(this);
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failed to unregister AdapterStateCallback with service");
+                    throw e.rethrowFromSystemServer();
                 }
                 mIsRegistered = false;
             }
@@ -115,24 +115,24 @@
                     mAdapter.setEnabled(isEnabled);
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failed to set adapter state");
-                    sendErrorState();
+                    throw e.rethrowFromSystemServer();
                 }
             }
         }
     }
 
-    private void sendErrorState() {
+    /**
+     * Gets the adapter enabled state
+     *
+     * @return integer representing adapter enabled state
+     */
+    public int getAdapterState() {
         synchronized (this) {
-            for (AdapterStateCallback callback: mCallbackMap.keySet()) {
-                Executor executor = mCallbackMap.get(callback);
-
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> callback.onStateChanged(
-                            mAdapterState, mAdapterStateChangeReason));
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
+            try {
+                return mAdapter.getAdapterState();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to get adapter state");
+                throw e.rethrowFromSystemServer();
             }
         }
     }
diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl
index 29021c1..d879350 100644
--- a/core/java/android/uwb/IUwbAdapter.aidl
+++ b/core/java/android/uwb/IUwbAdapter.aidl
@@ -161,6 +161,18 @@
      */
     void setEnabled(boolean enabled);
 
+   /**
+    * Returns the current enabled/disabled UWB state.
+    *
+    * Possible values are:
+    * IUwbAdapterState#STATE_DISABLED
+    * IUwbAdapterState#STATE_ENABLED_ACTIVE
+    * IUwbAdapterState#STATE_ENABLED_INACTIVE
+    *
+    * @return value representing enabled/disabled UWB state.
+    */
+   int getAdapterState();
+
   /**
    * The maximum allowed time to open a ranging session.
    */
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index 3c99f89..f7406ae 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -178,7 +178,7 @@
      * <p>The provided callback will be invoked by the given {@link Executor}.
      *
      * <p>When first registering a callback, the callbacks's
-     * {@link AdapterStateCallback#onStateChanged(boolean, int)} is immediately invoked to indicate
+     * {@link AdapterStateCallback#onStateChanged(int, int)} is immediately invoked to indicate
      * the current state of the underlying UWB adapter with the most recent
      * {@link AdapterStateCallback.StateChangedReason} that caused the change.
      *
@@ -279,6 +279,21 @@
     }
 
     /**
+     * Returns the current enabled/disabled state for UWB.
+     *
+     * Possible values are:
+     * AdapterStateCallback#STATE_DISABLED
+     * AdapterStateCallback#STATE_ENABLED_INACTIVE
+     * AdapterStateCallback#STATE_ENABLED_ACTIVE
+     *
+     * @return value representing current enabled/disabled state for UWB.
+     * @hide
+     */
+    public @AdapterStateCallback.State int getAdapterState() {
+        return mAdapterStateListener.getAdapterState();
+    }
+
+    /**
      * Disables or enables UWB for a user
      *
      * @param enabled value representing intent to disable or enable UWB. If true any subsequent
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index c87db65..9cb0d1f 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
+import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -36,6 +37,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
@@ -730,6 +732,15 @@
     }
 
     /**
+     * @return Brightness information about the display.
+     * @hide
+     */
+    @RequiresPermission(CONTROL_DISPLAY_BRIGHTNESS)
+    public @Nullable BrightnessInfo getBrightnessInfo() {
+        return mGlobal.getBrightnessInfo(mDisplayId);
+    }
+
+    /**
      * Gets the size of the display, in pixels.
      * Value returned by this method does not necessarily represent the actual raw size
      * (native resolution) of the display.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 88406ff..cd82489 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -857,7 +857,5 @@
      */
     void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener);
 
-    void setForceCrossWindowBlurDisabled(boolean disable);
-
     boolean isTaskSnapshotSupported();
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 60516eb..c32ab3a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -908,20 +908,6 @@
     }
 
     /**
-     * Disables cross-window blurs device-wide. This includes window blur behind
-     * (see {@link LayoutParams#setBlurBehindRadius}) and window background blur
-     * (see {@link Window#setBackgroundBlurRadius}).
-     *
-     * @param disable specifies whether to disable the blur. Note that calling this
-     *                with 'disable=false' will not enable blurs if there is something
-     *                else disabling blurs.
-     * @hide
-     */
-    @TestApi
-    default void setForceCrossWindowBlurDisabled(boolean disable) {
-    }
-
-    /**
      * @hide
      */
     static String transitTypeToString(@TransitionType int type) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 07eeb03..f8009919 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -328,15 +328,6 @@
     }
 
     @Override
-    public void setForceCrossWindowBlurDisabled(boolean disable) {
-        try {
-            WindowManagerGlobal.getWindowManagerService()
-                .setForceCrossWindowBlurDisabled(disable);
-        } catch (RemoteException e) {
-        }
-    }
-
-    @Override
     public boolean isTaskSnapshotSupported() {
         try {
             return WindowManagerGlobal.getWindowManagerService().isTaskSnapshotSupported();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d6292ca..d15aee0 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -266,6 +266,14 @@
     private static final int NOT_A_SUBTYPE_ID = -1;
 
     /**
+     * {@code true} to try to avoid blocking apps' UI thread by sending
+     * {@link StartInputReason#WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION} and
+     * {@link StartInputReason#WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION} in a truly asynchronous
+     * way. {@code false} to go back to the previous synchronous semantics.
+     */
+    private static final boolean USE_REPORT_WINDOW_GAINED_FOCUS_ASYNC = false;
+
+    /**
      * A constant that represents Voice IME.
      *
      * @see InputMethodSubtype#getMode()
@@ -689,20 +697,29 @@
                         Log.v(TAG, "Reporting focus gain, without startInput"
                                 + ", nextFocusIsServedView=" + nextFocusHasConnection);
                     }
-                    final int startInputReason =
-                            nextFocusHasConnection ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
-                                    : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
-                    final Completable.InputBindResult value = Completable.createInputBindResult();
-                    mService.startInputOrWindowGainedFocus(
-                            startInputReason, mClient,
-                            focusedView.getWindowToken(), startInputFlags, softInputMode,
-                            windowFlags,
-                            null,
-                            null,
-                            0 /* missingMethodFlags */,
-                            mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
-                            ResultCallbacks.of(value));
-                    Completable.getResult(value); // ignore the result
+
+                    if (USE_REPORT_WINDOW_GAINED_FOCUS_ASYNC) {
+                        mService.reportWindowGainedFocusAsync(
+                                nextFocusHasConnection, mClient, focusedView.getWindowToken(),
+                                startInputFlags, softInputMode, windowFlags,
+                                mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+                    } else {
+                        final int startInputReason = nextFocusHasConnection
+                                ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+                                : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+                        final Completable.InputBindResult value =
+                                Completable.createInputBindResult();
+                        mService.startInputOrWindowGainedFocus(
+                                startInputReason, mClient,
+                                focusedView.getWindowToken(), startInputFlags, softInputMode,
+                                windowFlags,
+                                null,
+                                null,
+                                0 /* missingMethodFlags */,
+                                mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+                                ResultCallbacks.of(value));
+                        Completable.getResult(value); // ignore the result
+                    }
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -1087,6 +1104,11 @@
         public void setImeTraceEnabled(boolean enabled) {
             ImeTracing.getInstance().setEnabled(enabled);
         }
+
+        @Override
+        public void throwExceptionFromSystem(String message) {
+            throw new RuntimeException(message);
+        }
     };
 
     final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index 560edec..5fbf228 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -22,6 +22,7 @@
 import android.view.autofill.AutofillId;
 import android.view.translation.TranslationContext;
 import android.view.translation.TranslationSpec;
+import android.view.translation.UiTranslationSpec;
 import com.android.internal.os.IResultReceiver;
 
 import java.util.List;
@@ -34,12 +35,14 @@
 oneway interface ITranslationManager {
     void onTranslationCapabilitiesRequest(int sourceFormat, int destFormat,
          in ResultReceiver receiver, int userId);
+    void registerTranslationCapabilityCallback(in IRemoteCallback callback, int userId);
+    void unregisterTranslationCapabilityCallback(in IRemoteCallback callback, int userId);
     void onSessionCreated(in TranslationContext translationContext,
          int sessionId, in IResultReceiver receiver, int userId);
 
     void updateUiTranslationState(int state, in TranslationSpec sourceSpec,
          in TranslationSpec targetSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
-         int userId);
+         in UiTranslationSpec uiTranslationSpec, int userId);
 
     void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
     void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 52790f6..e755774 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -16,6 +16,7 @@
 
 package android.view.translation;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -41,12 +42,14 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * The {@link TranslationManager} class provides ways for apps to integrate and use the
@@ -81,11 +84,14 @@
      */
     public static final String EXTRA_CAPABILITIES = "translation_capabilities";
 
-    // TODO: implement update listeners and propagate updates.
     @GuardedBy("mLock")
     private final ArrayMap<Pair<Integer, Integer>, ArrayList<PendingIntent>>
             mTranslationCapabilityUpdateListeners = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    private final Map<Consumer<TranslationCapability>, IRemoteCallback> mCapabilityCallbacks =
+            new ArrayMap<>();
+
     private static final Random ID_GENERATOR = new Random();
     private final Object mLock = new Object();
 
@@ -232,17 +238,43 @@
     }
 
     /**
-     * Registers a {@link PendingIntent} to listen for updates on states of on-device
+     * Adds a {@link TranslationCapability} Consumer to listen for updates on states of on-device
      * {@link TranslationCapability}s.
      *
-     * <p>IMPORTANT: the pending intent must be called to start a service, or a broadcast if it is
-     * an explicit intent.</p>
-     *
-     * @param sourceFormat data format for the input data to be translated.
-     * @param targetFormat data format for the expected translated output data.
-     * @param pendingIntent the pending intent to invoke when updates are received.
+     * @param capabilityListener a {@link TranslationCapability} Consumer to receive the updated
+     * {@link TranslationCapability} from the on-device translation service.
      */
     public void addOnDeviceTranslationCapabilityUpdateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TranslationCapability> capabilityListener) {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(capabilityListener, "capability listener should not be null");
+
+        synchronized (mLock) {
+            if (mCapabilityCallbacks.containsKey(capabilityListener)) {
+                Log.w(TAG, "addOnDeviceTranslationCapabilityUpdateListener: the listener for "
+                        + capabilityListener + " already registered; ignoring.");
+                return;
+            }
+            final IRemoteCallback remoteCallback = new TranslationCapabilityRemoteCallback(executor,
+                    capabilityListener);
+            try {
+                mService.registerTranslationCapabilityCallback(remoteCallback,
+                        mContext.getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCapabilityCallbacks.put(capabilityListener, remoteCallback);
+        }
+    }
+
+
+    /**
+     * @deprecated Use {@link TranslationManager#addOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.concurrent.Executor, java.util.function.Consumer)}
+     */
+    @Deprecated
+    public void addOnDeviceTranslationCapabilityUpdateListener(
             @TranslationSpec.DataFormat int sourceFormat,
             @TranslationSpec.DataFormat int targetFormat,
             @NonNull PendingIntent pendingIntent) {
@@ -256,8 +288,8 @@
     }
 
     /**
-     * @deprecated Use {@link #addOnDeviceTranslationCapabilityUpdateListener(int, int,
-     *  PendingIntent)}
+     * @deprecated Use {@link TranslationManager#addOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.concurrent.Executor, java.util.function.Consumer)}
      */
     @Deprecated
     public void addTranslationCapabilityUpdateListener(
@@ -268,14 +300,38 @@
     }
 
     /**
-     * Unregisters a {@link PendingIntent} to listen for updates on states of on-device
-     * {@link TranslationCapability}s.
+     * Removes a {@link TranslationCapability} Consumer to listen for updates on states of
+     * on-device {@link TranslationCapability}s.
      *
-     * @param sourceFormat data format for the input data to be translated.
-     * @param targetFormat data format for the expected translated output data.
-     * @param pendingIntent the pending intent to unregister
+     * @param capabilityListener the {@link TranslationCapability} Consumer to unregister
      */
     public void removeOnDeviceTranslationCapabilityUpdateListener(
+            @NonNull Consumer<TranslationCapability> capabilityListener) {
+        Objects.requireNonNull(capabilityListener, "capability callback should not be null");
+
+        synchronized (mLock) {
+            final IRemoteCallback remoteCallback = mCapabilityCallbacks.get(capabilityListener);
+            if (remoteCallback == null) {
+                Log.w(TAG, "removeOnDeviceTranslationCapabilityUpdateListener: the capability "
+                        + "listener not found; ignoring.");
+                return;
+            }
+            try {
+                mService.unregisterTranslationCapabilityCallback(remoteCallback,
+                        mContext.getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCapabilityCallbacks.remove(capabilityListener);
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #removeOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.function.Consumer)}.
+     */
+    @Deprecated
+    public void removeOnDeviceTranslationCapabilityUpdateListener(
             @TranslationSpec.DataFormat int sourceFormat,
             @TranslationSpec.DataFormat int targetFormat,
             @NonNull PendingIntent pendingIntent) {
@@ -300,8 +356,8 @@
     }
 
     /**
-     * @deprecated Use {@link #removeOnDeviceTranslationCapabilityUpdateListener(int, int,
-     *  PendingIntent)}
+     * @deprecated Use {@link #removeOnDeviceTranslationCapabilityUpdateListener(
+     * java.util.function.Consumer)}.
      */
     @Deprecated
     public void removeTranslationCapabilityUpdateListener(
@@ -366,9 +422,12 @@
     private static class TranslationCapabilityRemoteCallback extends
             IRemoteCallback.Stub {
         private final Executor mExecutor;
+        private final Consumer<TranslationCapability> mListener;
 
-        TranslationCapabilityRemoteCallback(Executor executor) {
+        TranslationCapabilityRemoteCallback(Executor executor,
+                Consumer<TranslationCapability> listener) {
             mExecutor = executor;
+            mListener = listener;
         }
 
         @Override
@@ -378,9 +437,9 @@
         }
 
         private void onTranslationCapabilityUpdate(Bundle bundle) {
-            TranslationCapability capability = (TranslationCapability) bundle.getParcelable(
-                    EXTRA_CAPABILITIES);
-            //TODO: Implement after deciding how capability listeners are implemented.
+            TranslationCapability capability =
+                    (TranslationCapability) bundle.getParcelable(EXTRA_CAPABILITIES);
+            mListener.accept(capability);
         }
     }
 }
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index eefc7fd..541b494 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -119,15 +119,31 @@
     }
 
     /**
+     * @deprecated Use {@link #startTranslation(TranslationSpec, TranslationSpec, List, ActivityId,
+     * UiTranslationSpec)} instead.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+    @Deprecated
+    @SystemApi
+    public void startTranslation(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec targetSpec, @NonNull List<AutofillId> viewIds,
+            @NonNull ActivityId activityId) {
+        startTranslation(
+                sourceSpec, targetSpec, viewIds, activityId,
+                new UiTranslationSpec.Builder().setShouldPadContentForCompat(true).build());
+    }
+
+    /**
      * Request ui translation for a given Views.
      *
      * @param sourceSpec {@link TranslationSpec} for the data to be translated.
      * @param targetSpec {@link TranslationSpec} for the translated data.
      * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated
      * @param activityId the identifier for the Activity which needs ui translation
+     * @param uiTranslationSpec configuration for translation of the specified views
      * @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list
-     * @throws NullPointerException the sourceSpec, targetSpec, viewIds, activityId or
-     *         {@link android.app.assist.ActivityId#getToken()} is {@code null}
      *
      * @hide
      */
@@ -135,19 +151,21 @@
     @SystemApi
     public void startTranslation(@NonNull TranslationSpec sourceSpec,
             @NonNull TranslationSpec targetSpec, @NonNull List<AutofillId> viewIds,
-            @NonNull ActivityId activityId) {
+            @NonNull ActivityId activityId, @NonNull UiTranslationSpec uiTranslationSpec) {
         // TODO(b/177789967): Return result code or find a way to notify the status.
         Objects.requireNonNull(sourceSpec);
         Objects.requireNonNull(targetSpec);
         Objects.requireNonNull(viewIds);
         Objects.requireNonNull(activityId);
         Objects.requireNonNull(activityId.getToken());
+        Objects.requireNonNull(uiTranslationSpec);
         if (viewIds.size() == 0) {
             throw new IllegalArgumentException("Invalid empty views: " + viewIds);
         }
         try {
             mService.updateUiTranslationState(STATE_UI_TRANSLATION_STARTED, sourceSpec,
                     targetSpec, viewIds, activityId.getToken(), activityId.getTaskId(),
+                    uiTranslationSpec,
                     mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -172,7 +190,8 @@
             Objects.requireNonNull(activityId.getToken());
             mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED,
                     null /* sourceSpec */, null /* targetSpec */, null /* viewIds */,
-                    activityId.getToken(), activityId.getTaskId(), mContext.getUserId());
+                    activityId.getToken(), activityId.getTaskId(), null /* uiTranslationSpec */,
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -196,7 +215,8 @@
             Objects.requireNonNull(activityId.getToken());
             mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED,
                     null /* sourceSpec */, null /* targetSpec */, null /* viewIds */,
-                    activityId.getToken(), activityId.getTaskId(), mContext.getUserId());
+                    activityId.getToken(), activityId.getTaskId(), null /* uiTranslationSpec */,
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -220,7 +240,8 @@
             Objects.requireNonNull(activityId.getToken());
             mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED,
                     null /* sourceSpec */, null /* targetSpec */, null /* viewIds */,
-                    activityId.getToken(), activityId.getTaskId(), mContext.getUserId());
+                    activityId.getToken(), activityId.getTaskId(), null /* uiTranslationSpec */,
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/translation/UiTranslationSpec.aidl b/core/java/android/view/translation/UiTranslationSpec.aidl
new file mode 100644
index 0000000..7fbeb66
--- /dev/null
+++ b/core/java/android/view/translation/UiTranslationSpec.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.translation;
+
+parcelable UiTranslationSpec;
diff --git a/core/java/android/view/translation/UiTranslationSpec.java b/core/java/android/view/translation/UiTranslationSpec.java
new file mode 100644
index 0000000..b43dbce
--- /dev/null
+++ b/core/java/android/view/translation/UiTranslationSpec.java
@@ -0,0 +1,248 @@
+/*
+ * 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 android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Specifications for configuring UI translation.
+ *
+ * @hide
+ */
+@DataClass(
+        genBuilder = true, genEqualsHashCode = true, genHiddenConstDefs = true, genToString = true)
+@DataClass.Suppress("isShouldPadContentForCompat")
+@SystemApi
+public final class UiTranslationSpec implements Parcelable {
+
+    /**
+     * Whether the original content of the view should be directly modified to include padding that
+     * makes it the same size as the translated content. Defaults to {@code false}.
+     * <p>
+     * For {@link android.widget.TextView}, the system does not directly modify the original text,
+     * rather changes the displayed content using a
+     * {@link android.text.method.TransformationMethod}.
+     * This can cause issues in apps that do not account for TransformationMethods. For example, an
+     * app using DynamicLayout may use the calculated text offsets to operate on the original text,
+     * but this can be problematic when the layout was calculated on translated text with a
+     * different length.
+     * <p>
+     * If this is {@code true}, for a TextView the default implementation will append spaces to the
+     * text to make the length the same as the translated text.
+     */
+    private boolean mShouldPadContentForCompat = false;
+
+    /**
+     * Whether the original content of the view should be directly modified to include padding that
+     * makes it the same size as the translated content.
+     * <p>
+     * For {@link android.widget.TextView}, the system does not directly modify the original text,
+     * rather changes the displayed content using a
+     * {@link android.text.method.TransformationMethod}.
+     * This can cause issues in apps that do not account for TransformationMethods. For example, an
+     * app using DynamicLayout may use the calculated text offsets to operate on the original text,
+     * but this can be problematic when the layout was calculated on translated text with a
+     * different length.
+     * <p>
+     * If this is {@code true}, for a TextView the default implementation will append spaces to the
+     * text to make the length the same as the translated text.
+     */
+    public boolean shouldPadContentForCompat() {
+        return mShouldPadContentForCompat;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/UiTranslationSpec.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ UiTranslationSpec(
+            boolean shouldPadContentForCompat) {
+        this.mShouldPadContentForCompat = shouldPadContentForCompat;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "UiTranslationSpec { " +
+                "shouldPadContentForCompat = " + mShouldPadContentForCompat +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(UiTranslationSpec other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        UiTranslationSpec that = (UiTranslationSpec) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mShouldPadContentForCompat == that.mShouldPadContentForCompat;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Boolean.hashCode(mShouldPadContentForCompat);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mShouldPadContentForCompat) flg |= 0x1;
+        dest.writeByte(flg);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ UiTranslationSpec(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean shouldPadContentForCompat = (flg & 0x1) != 0;
+
+        this.mShouldPadContentForCompat = shouldPadContentForCompat;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<UiTranslationSpec> CREATOR
+            = new Parcelable.Creator<UiTranslationSpec>() {
+        @Override
+        public UiTranslationSpec[] newArray(int size) {
+            return new UiTranslationSpec[size];
+        }
+
+        @Override
+        public UiTranslationSpec createFromParcel(@NonNull Parcel in) {
+            return new UiTranslationSpec(in);
+        }
+    };
+
+    /**
+     * A builder for {@link UiTranslationSpec}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private boolean mShouldPadContentForCompat;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Whether the original content of the view should be directly modified to include padding that
+         * makes it the same size as the translated content. Defaults to {@code false}.
+         * <p>
+         * For {@link android.widget.TextView}, the system does not directly modify the original text,
+         * rather changes the displayed content using a
+         * {@link android.text.method.TransformationMethod}.
+         * This can cause issues in apps that do not account for TransformationMethods. For example, an
+         * app using DynamicLayout may use the calculated text offsets to operate on the original text,
+         * but this can be problematic when the layout was calculated on translated text with a
+         * different length.
+         * <p>
+         * If this is {@code true}, for a TextView the default implementation will append spaces to the
+         * text to make the length the same as the translated text.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setShouldPadContentForCompat(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mShouldPadContentForCompat = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull UiTranslationSpec build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mShouldPadContentForCompat = false;
+            }
+            UiTranslationSpec o = new UiTranslationSpec(
+                    mShouldPadContentForCompat);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x2) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1619034161701L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/translation/UiTranslationSpec.java",
+            inputSignatures = "private  boolean mShouldPadContentForCompat\npublic  boolean shouldPadContentForCompat()\nclass UiTranslationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index 18f29ae..7d222db 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -17,12 +17,17 @@
 package android.window;
 
 import android.annotation.NonNull;
+import android.annotation.StyleRes;
 import android.annotation.SuppressLint;
 import android.annotation.UiThread;
 import android.app.Activity;
 import android.app.ActivityThread;
+import android.app.AppGlobals;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
 import android.util.Singleton;
 import android.util.Slog;
 
@@ -60,6 +65,17 @@
      */
     void clearOnExitAnimationListener();
 
+
+    /**
+     * Overrides the theme used for the {@link SplashScreen}s of this application.
+     * <p>
+     * By default, the {@link SplashScreen} uses the theme set in the manifest. This method
+     * overrides and persists the theme used for the {@link SplashScreen} of this application.
+     * <p>
+     * To reset to the default theme, set this the themeId to {@link Resources#ID_NULL}.
+     */
+    void setSplashScreenTheme(@StyleRes int themeId);
+
     /**
      * Listens for the splash screen exit event.
      */
@@ -84,6 +100,8 @@
      * @hide
      */
     class SplashScreenImpl implements SplashScreen {
+        private static final String TAG = "SplashScreenImpl";
+
         private OnExitAnimationListener mExitAnimationListener;
         private final IBinder mActivityToken;
         private final SplashScreenManagerGlobal mGlobal;
@@ -119,6 +137,29 @@
                 mGlobal.removeImpl(this);
             }
         }
+
+        public void setSplashScreenTheme(@StyleRes int themeId) {
+            if (mActivityToken == null) {
+                Log.w(TAG, "Couldn't persist the starting theme. This instance is not an Activity");
+                return;
+            }
+
+            Activity activity = ActivityThread.currentActivityThread().getActivity(
+                    mActivityToken);
+            if (activity == null) {
+                return;
+            }
+            String themeName = themeId != Resources.ID_NULL
+                    ? activity.getResources().getResourceName(themeId) : null;
+
+            try {
+                AppGlobals.getPackageManager().setSplashScreenTheme(
+                        activity.getComponentName().getPackageName(),
+                        themeName, activity.getUserId());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Couldn't persist the starting theme", e);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 26a6f0d..f93e413 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -109,8 +109,8 @@
     }
 
     /**
-     * Notify activities within the hierarchy of a container that they have entered picture-in-picture
-     * mode with the given bounds.
+     * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
+     * has finished the enter animation with the given bounds.
      */
     @NonNull
     public WindowContainerTransaction scheduleFinishEnterPip(
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
index 375e503..f34aabb 100644
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.app;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -69,6 +71,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
         // Set up the "dialog"
         final Intent intent = getIntent();
         final AlertController.AlertParams p = mAlertParams;
diff --git a/core/java/com/android/internal/colorextraction/OWNERS b/core/java/com/android/internal/colorextraction/OWNERS
new file mode 100644
index 0000000..ffade1e
--- /dev/null
+++ b/core/java/com/android/internal/colorextraction/OWNERS
@@ -0,0 +1,3 @@
+dupin@google.com
+cinek@google.com
+jamesoleary@google.com
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 6776c27..bd90890 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -16,11 +16,11 @@
 
 package com.android.internal.display;
 
-
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
@@ -95,7 +95,7 @@
         }
 
         final BrightnessSyncObserver brightnessSyncObserver;
-        brightnessSyncObserver = new BrightnessSyncObserver(mHandler);
+        brightnessSyncObserver = new BrightnessSyncObserver();
         brightnessSyncObserver.startObserving();
 
         final float currentFloatBrightness = getScreenBrightnessFloat();
@@ -232,47 +232,52 @@
         }
     }
 
-    private class BrightnessSyncObserver extends ContentObserver {
-        /**
-         * Creates a content observer.
-         * @param handler The handler to run {@link #onChange} on, or null if none.
-         */
-        BrightnessSyncObserver(Handler handler) {
-            super(handler);
-        }
+    private class BrightnessSyncObserver {
+        private final DisplayListener mListener = new DisplayListener() {
+            @Override
+            public void onDisplayAdded(int displayId) {}
 
-        @Override
-        public void onChange(boolean selfChange) {
-            onChange(selfChange, null);
-        }
+            @Override
+            public void onDisplayRemoved(int displayId) {}
 
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (selfChange) {
-                return;
-            }
-            if (BRIGHTNESS_URI.equals(uri)) {
-                int currentBrightness = getScreenBrightnessInt(mContext);
-                mHandler.removeMessages(MSG_UPDATE_FLOAT);
-                mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
-            } else if (BRIGHTNESS_FLOAT_URI.equals(uri)) {
+            @Override
+            public void onDisplayChanged(int displayId) {
                 float currentFloat = getScreenBrightnessFloat();
                 int toSend = Float.floatToIntBits(currentFloat);
                 mHandler.removeMessages(MSG_UPDATE_INT);
                 mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget();
             }
-        }
+        };
+
+        private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                if (selfChange) {
+                    return;
+                }
+                if (BRIGHTNESS_URI.equals(uri)) {
+                    int currentBrightness = getScreenBrightnessInt(mContext);
+                    mHandler.removeMessages(MSG_UPDATE_FLOAT);
+                    mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
+                }
+            }
+        };
 
         public void startObserving() {
             final ContentResolver cr = mContext.getContentResolver();
-            cr.unregisterContentObserver(this);
-            cr.registerContentObserver(BRIGHTNESS_URI, false, this, UserHandle.USER_ALL);
-            cr.registerContentObserver(BRIGHTNESS_FLOAT_URI, false, this, UserHandle.USER_ALL);
+            cr.unregisterContentObserver(mContentObserver);
+            cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
+                    UserHandle.USER_ALL);
+
+            mDisplayManager.registerDisplayListener(mListener, mHandler,
+                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
         public void stopObserving() {
             final ContentResolver cr = mContext.getContentResolver();
-            cr.unregisterContentObserver(this);
+            cr.unregisterContentObserver(mContentObserver);
+            mDisplayManager.unregisterDisplayListener(mListener);
         }
     }
 }
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 49dbbaa..a61e86b 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -30,4 +30,5 @@
     void reportFullscreenMode(boolean fullscreen);
     void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues);
     void setImeTraceEnabled(boolean enabled);
+    void throwExceptionFromSystem(String message);
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 93cd4e9..772e344 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -68,6 +68,12 @@
             int unverifiedTargetSdkVersion,
             in IInputBindResultResultCallback inputBindResult);
 
+    oneway void reportWindowGainedFocusAsync(
+            boolean nextFocusHasConnection, in IInputMethodClient client, in IBinder windowToken,
+            /* @StartInputFlags */ int startInputFlags,
+            /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
+            int windowFlags, int unverifiedTargetSdkVersion);
+
     oneway void showInputMethodPickerFromClient(in IInputMethodClient client,
             int auxiliarySubtypeMode, in IVoidResultCallback resultCallback);
     oneway void showInputMethodPickerFromSystem(in IInputMethodClient client,
diff --git a/core/proto/android/providers/OWNERS b/core/proto/android/providers/OWNERS
new file mode 100644
index 0000000..1f5cd9a
--- /dev/null
+++ b/core/proto/android/providers/OWNERS
@@ -0,0 +1 @@
+include /packages/SettingsProvider/OWNERS
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 8ee0e39..c3d1596 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -285,6 +285,7 @@
         // Deprecated, use enable_non_resizable_multi_window
         optional SettingProto enable_sizecompat_freeform = 7 [ (android.privacy).dest = DEST_AUTOMATIC, deprecated = true ];
         optional SettingProto enable_non_resizable_multi_window = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto disable_window_blurs = 9 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Development development = 39;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e4f16a9..9c65dac 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2709,10 +2709,9 @@
     <permission android:name="android.permission.CREATE_USERS"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi @hide Allows an application to access data blobs across users.
-         This permission is not available to third party applications. -->
+    <!-- Allows an application to access data blobs across users. -->
     <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
-        android:protectionLevel="signature|privileged|development" />
+        android:protectionLevel="signature|privileged|development|role" />
 
     <!-- @hide Allows an application to set the profile owners and the device owner.
          This permission is not available to third party applications.-->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 529aa66..fbc9678 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -657,6 +657,12 @@
     <!-- Indicate the display area rect for foldable devices in folded state. -->
     <string name="config_foldedArea"></string>
 
+    <!-- Indicates that the device supports having more than one internal display on at the same
+         time. Only applicable to devices with more than one internal display. If this option is
+         set to false, DisplayManager will make additional effort to ensure no more than 1 internal
+         display is powered on at the same time. -->
+    <bool name="config_supportsConcurrentInternalDisplays">true</bool>
+
     <!-- Desk dock behavior -->
 
     <!-- The number of degrees to rotate the display when the device is in a desk dock.
@@ -4854,17 +4860,30 @@
     <!-- Whether app hibernation deletes OAT artifact files as part of global hibernation. -->
     <bool name="config_hibernationDeletesOatArtifactsEnabled">true</bool>
 
-    <!-- On-device intelligent processor for system UI features. -->
+    <!-- Package name of the on-device intelligent processor for system UI
+         features. Examples include the search functionality or the app
+         predictor. -->
     <string name="config_systemUiIntelligence" translatable="false"></string>
-    <!-- On-device intelligent processor for ambient audio. -->
+    <!-- Package name of the on-device intelligent processor for ambient audio.
+         Ambient audio is the sound surrounding the device captured by the DSP
+         or the microphone. In other words, the device is continuously
+         processing audio data in background. -->
     <string name="config_systemAmbientAudioIntelligence" translatable="false"></string>
-    <!-- On-device intelligent processor for audio. -->
+    <!-- Package name of the on-device intelligent processor for audio. The
+         difference of 'ambient audio' and 'audio' is that in 'audio', the
+         user intentionally and consciously aware that the device is recording
+         or using the microphone.
+         -->
     <string name="config_systemAudioIntelligence" translatable="false"></string>
-    <!-- On-device intelligent processor for notification. -->
+    <!-- Package name of the on-device intelligent processor for notification.
+         -->
     <string name="config_systemNotificationIntelligence" translatable="false"></string>
-    <!-- On-device intelligent processor for text. -->
+    <!-- Package name of the on-device intelligent processor for text. Examples
+        include providing autofill functionality based on incoming text
+        messages. -->
     <string name="config_systemTextIntelligence" translatable="false"></string>
-    <!-- On-device intelligent processor for visual features. -->
+    <!-- Package name of the on-device intelligent processor for visual
+         features. Examples include the autorotate feature. -->
     <string name="config_systemVisualIntelligence" translatable="false"></string>
     <!-- On-device package for providing companion device associations. -->
     <string name="config_systemCompanionDeviceProvider" translatable="false"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 78a794a..9ac9e8e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3800,6 +3800,7 @@
   <!-- For Foldables -->
   <java-symbol type="array" name="config_foldedDeviceStates" />
   <java-symbol type="string" name="config_foldedArea" />
+  <java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
 
   <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
   <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 2e2e6bd..6f17ea9 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -185,15 +185,6 @@
     }
 
     @Test
-    public void testHandleActivity_assetsChanged() {
-        relaunchActivityAndAssertPreserveWindow(activity -> {
-            // Relaunches all activities.
-            activity.getActivityThread().handleApplicationInfoChanged(
-                    activity.getApplicationInfo());
-        });
-    }
-
-    @Test
     public void testRecreateActivity() {
         relaunchActivityAndAssertPreserveWindow(Activity::recreate);
     }
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index b2b9ab3..c9a18da 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -30,6 +30,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -284,33 +285,42 @@
         PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
         PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
         PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+        PasswordMetrics pin = new PasswordMetrics(CREDENTIAL_TYPE_PIN);
 
         // To pass minimal length check.
         password.length = 4;
+        pin.length = 4;
 
         // No errors expected, credential is of stronger or equal type.
         assertValidationErrors(
-                validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, none));
+                validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, none));
         assertValidationErrors(
-                validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, pattern));
+                validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, pattern));
         assertValidationErrors(
-                validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, password));
+                validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, password));
         assertValidationErrors(
-                validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, pattern));
+                validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, pin));
         assertValidationErrors(
-                validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, password));
+                validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, pattern));
         assertValidationErrors(
-                validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, password));
+                validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, password));
+        assertValidationErrors(
+                validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, password));
+        assertValidationErrors(
+                validatePasswordMetrics(pin, PASSWORD_COMPLEXITY_NONE, pin));
 
         // Now actual credential type is weaker than required:
         assertValidationErrors(
-                validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, none),
+                validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, none),
                 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
         assertValidationErrors(
-                validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, none),
+                validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, none),
                 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
         assertValidationErrors(
-                validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, pattern),
+                validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, pattern),
+                PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+        assertValidationErrors(
+                validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, pin),
                 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
     }
 
diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
index 36da927..3e2c4e9 100644
--- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -234,6 +234,7 @@
                 .setIsImportantConversation(true)
                 .setStatuses(statusList).setNotificationKey("key")
                 .setNotificationContent("content")
+                .setNotificationSender("sender")
                 .setNotificationDataUri(Uri.parse("data"))
                 .setMessagesCount(2)
                 .setIntent(new Intent())
@@ -256,6 +257,7 @@
         assertThat(readTile.getStatuses()).isEqualTo(tile.getStatuses());
         assertThat(readTile.getNotificationKey()).isEqualTo(tile.getNotificationKey());
         assertThat(readTile.getNotificationContent()).isEqualTo(tile.getNotificationContent());
+        assertThat(readTile.getNotificationSender()).isEqualTo(tile.getNotificationSender());
         assertThat(readTile.getNotificationDataUri()).isEqualTo(tile.getNotificationDataUri());
         assertThat(readTile.getMessagesCount()).isEqualTo(tile.getMessagesCount());
         assertThat(readTile.getIntent().toString()).isEqualTo(tile.getIntent().toString());
@@ -282,6 +284,16 @@
     }
 
     @Test
+    public void testNotificationSender() {
+        PeopleSpaceTile tile = new PeopleSpaceTile
+                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+                .setNotificationSender("test")
+                .build();
+
+        assertThat(tile.getNotificationSender()).isEqualTo("test");
+    }
+
+    @Test
     public void testNotificationDataUri() {
         PeopleSpaceTile tile =
                 new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build(),
diff --git a/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java b/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java
index bdaf630..4cad535 100644
--- a/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java
+++ b/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java
@@ -19,7 +19,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -114,30 +113,6 @@
     }
 
     @Test
-    public void testRegister_FirstRegisterFails() throws RemoteException {
-        AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
-        AdapterStateCallback callback1 = mock(AdapterStateCallback.class);
-        AdapterStateCallback callback2 = mock(AdapterStateCallback.class);
-
-        // Throw a remote exception whenever first registering
-        doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any());
-
-        adapterStateListener.register(getExecutor(), callback1);
-        verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
-
-        // No longer throw an exception, instead succeed
-        doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
-
-        // Register a different callback
-        adapterStateListener.register(getExecutor(), callback2);
-        verify(mUwbAdapter, times(2)).registerAdapterStateCallbacks(any());
-
-        // Ensure first callback was invoked again
-        verifyCallbackStateChangedInvoked(callback1, 2);
-        verifyCallbackStateChangedInvoked(callback2, 1);
-    }
-
-    @Test
     public void testRegister_RegisterSameCallbackTwice() throws RemoteException {
         AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
         AdapterStateCallback callback = mock(AdapterStateCallback.class);
@@ -162,13 +137,6 @@
         runViaExecutor();
     }
 
-    @Test
-    public void testCallback_RunViaExecutor_Failure() throws RemoteException {
-        // Verify that the callbacks are invoked on the executor when there is a remote exception
-        doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any());
-        runViaExecutor();
-    }
-
     private void runViaExecutor() {
         AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
         AdapterStateCallback callback = mock(AdapterStateCallback.class);
diff --git a/data/keyboards/Vendor_054c_Product_0268.kl b/data/keyboards/Vendor_054c_Product_0268.kl
index b463dd8..08d1c34 100644
--- a/data/keyboards/Vendor_054c_Product_0268.kl
+++ b/data/keyboards/Vendor_054c_Product_0268.kl
@@ -77,3 +77,11 @@
 key 0x123    BUTTON_START
 # PS key
 key 0x2d0    BUTTON_MODE
+
+# SENSORs
+sensor 0x00 ACCELEROMETER X
+sensor 0x01 ACCELEROMETER Y
+sensor 0x02 ACCELEROMETER Z
+sensor 0x03 GYROSCOPE X
+sensor 0x04 GYROSCOPE Y
+sensor 0x05 GYROSCOPE Z
diff --git a/data/keyboards/Vendor_054c_Product_0268_Version_8000.kl b/data/keyboards/Vendor_054c_Product_0268_Version_8000.kl
index 3d93f0f..d281b4b 100644
--- a/data/keyboards/Vendor_054c_Product_0268_Version_8000.kl
+++ b/data/keyboards/Vendor_054c_Product_0268_Version_8000.kl
@@ -55,3 +55,11 @@
 key 0x13b   BUTTON_START
 # PS key
 key 0x13c   BUTTON_MODE
+
+# SENSORs
+sensor 0x00 ACCELEROMETER X
+sensor 0x01 ACCELEROMETER Y
+sensor 0x02 ACCELEROMETER Z
+sensor 0x03 GYROSCOPE X
+sensor 0x04 GYROSCOPE Y
+sensor 0x05 GYROSCOPE Z
diff --git a/data/keyboards/Vendor_054c_Product_0268_Version_8100.kl b/data/keyboards/Vendor_054c_Product_0268_Version_8100.kl
index 3d93f0f..d281b4b 100644
--- a/data/keyboards/Vendor_054c_Product_0268_Version_8100.kl
+++ b/data/keyboards/Vendor_054c_Product_0268_Version_8100.kl
@@ -55,3 +55,11 @@
 key 0x13b   BUTTON_START
 # PS key
 key 0x13c   BUTTON_MODE
+
+# SENSORs
+sensor 0x00 ACCELEROMETER X
+sensor 0x01 ACCELEROMETER Y
+sensor 0x02 ACCELEROMETER Z
+sensor 0x03 GYROSCOPE X
+sensor 0x04 GYROSCOPE Y
+sensor 0x05 GYROSCOPE Z
diff --git a/data/keyboards/Vendor_054c_Product_0268_Version_8111.kl b/data/keyboards/Vendor_054c_Product_0268_Version_8111.kl
index 5fe35f7..3eafea0 100644
--- a/data/keyboards/Vendor_054c_Product_0268_Version_8111.kl
+++ b/data/keyboards/Vendor_054c_Product_0268_Version_8111.kl
@@ -55,3 +55,11 @@
 key 0x13b   BUTTON_START
 # PS key
 key 0x13c   BUTTON_MODE
+
+# SENSORs
+sensor 0x00 ACCELEROMETER X
+sensor 0x01 ACCELEROMETER Y
+sensor 0x02 ACCELEROMETER Z
+sensor 0x03 GYROSCOPE X
+sensor 0x04 GYROSCOPE Y
+sensor 0x05 GYROSCOPE Z
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 8ac9a7a..ca05ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -427,35 +427,43 @@
                 Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
                 @PipAnimationController.TransitionDirection int direction, float startingAngle,
                 @Surface.Rotation int rotationDelta) {
+            final boolean isOutPipDirection = isOutPipDirection(direction);
+
             // Just for simplicity we'll interpolate between the source rect hint insets and empty
             // insets to calculate the window crop
             final Rect initialSourceValue;
-            if (isOutPipDirection(direction)) {
+            if (isOutPipDirection) {
                 initialSourceValue = new Rect(endValue);
             } else {
                 initialSourceValue = new Rect(baseValue);
             }
 
+            final Rect rotatedEndRect;
+            final Rect lastEndRect;
+            final Rect initialContainerRect;
+            if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
+                lastEndRect = new Rect(endValue);
+                rotatedEndRect = new Rect(endValue);
+                // Rotate the end bounds according to the rotation delta because the display will
+                // be rotated to the same orientation.
+                rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+                // Use the rect that has the same orientation as the hint rect.
+                initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
+            } else {
+                rotatedEndRect = lastEndRect = null;
+                initialContainerRect = initialSourceValue;
+            }
+
             final Rect sourceHintRectInsets;
             if (sourceHintRect == null) {
                 sourceHintRectInsets = null;
             } else {
-                sourceHintRectInsets = new Rect(sourceHintRect.left - initialSourceValue.left,
-                        sourceHintRect.top - initialSourceValue.top,
-                        initialSourceValue.right - sourceHintRect.right,
-                        initialSourceValue.bottom - sourceHintRect.bottom);
+                sourceHintRectInsets = new Rect(sourceHintRect.left - initialContainerRect.left,
+                        sourceHintRect.top - initialContainerRect.top,
+                        initialContainerRect.right - sourceHintRect.right,
+                        initialContainerRect.bottom - sourceHintRect.bottom);
             }
-            final Rect sourceInsets = new Rect(0, 0, 0, 0);
-
-            final Rect rotatedEndRect;
-            if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
-                // Rotate the end bounds according to the rotation delta because the display will
-                // be rotated to the same orientation.
-                rotatedEndRect = new Rect(endValue);
-                rotateBounds(rotatedEndRect, endValue, rotationDelta);
-            } else {
-                rotatedEndRect = null;
-            }
+            final Rect zeroInsets = new Rect(0, 0, 0, 0);
 
             // construct new Rect instances in case they are recycled
             return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
@@ -472,8 +480,8 @@
                     final Rect end = getEndValue();
                     if (rotatedEndRect != null) {
                         // Animate the bounds in a different orientation. It only happens when
-                        // leaving PiP to fullscreen.
-                        applyRotation(tx, leash, fraction, start, end, rotatedEndRect);
+                        // switching between PiP and fullscreen.
+                        applyRotation(tx, leash, fraction, start, end);
                         return;
                     }
                     Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
@@ -481,20 +489,13 @@
                     setCurrentValue(bounds);
                     if (inScaleTransition() || sourceHintRect == null) {
 
-                        if (isOutPipDirection(direction)) {
+                        if (isOutPipDirection) {
                             getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
                         } else {
                             getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle);
                         }
                     } else {
-                        final Rect insets;
-                        if (isOutPipDirection(direction)) {
-                            insets = mInsetsEvaluator.evaluate(fraction, sourceHintRectInsets,
-                                    sourceInsets);
-                        } else {
-                            insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
-                                    sourceHintRectInsets);
-                        }
+                        final Rect insets = computeInsets(fraction);
                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
                                 initialSourceValue, bounds, insets);
                     }
@@ -502,9 +503,17 @@
                 }
 
                 private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
-                        float fraction, Rect start, Rect end, Rect rotatedEndRect) {
+                        float fraction, Rect start, Rect end) {
+                    if (!end.equals(lastEndRect)) {
+                        // If the end bounds are changed during animating (e.g. shelf height), the
+                        // rotated end bounds also need to be updated.
+                        rotatedEndRect.set(endValue);
+                        rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+                        lastEndRect.set(end);
+                    }
                     final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect);
                     setCurrentValue(bounds);
+                    final Rect insets = computeInsets(fraction);
                     final float degree, x, y;
                     if (rotationDelta == ROTATION_90) {
                         degree = 90 * fraction;
@@ -515,11 +524,21 @@
                         x = fraction * (end.left - start.left) + start.left;
                         y = fraction * (end.bottom - start.top) + start.top;
                     }
-                    getSurfaceTransactionHelper().rotateAndScaleWithCrop(tx, leash, bounds,
-                            rotatedEndRect, degree, x, y);
+                    getSurfaceTransactionHelper().rotateAndScaleWithCrop(tx, leash,
+                            initialContainerRect, bounds, insets, degree, x, y, isOutPipDirection,
+                            rotationDelta == ROTATION_270 /* clockwise */);
                     tx.apply();
                 }
 
+                private Rect computeInsets(float fraction) {
+                    if (sourceHintRectInsets == null) {
+                        return zeroInsets;
+                    }
+                    final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets;
+                    final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets;
+                    return mInsetsEvaluator.evaluate(fraction, startRect, endRect);
+                }
+
                 @Override
                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
                     getSurfaceTransactionHelper()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3dd97f5..2b79539 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -137,23 +137,41 @@
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
     public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx,
-            SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees,
-            float positionX, float positionY) {
+            SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets,
+            float degrees, float positionX, float positionY, boolean isExpanding,
+            boolean clockwise) {
         mTmpDestinationRect.set(sourceBounds);
-        final int dw = destinationBounds.width();
-        final int dh = destinationBounds.height();
+        mTmpDestinationRect.inset(insets);
+        final int srcW = mTmpDestinationRect.width();
+        final int srcH = mTmpDestinationRect.height();
+        final int destW = destinationBounds.width();
+        final int destH = destinationBounds.height();
         // Scale by the short side so there won't be empty area if the aspect ratio of source and
         // destination are different.
-        final float scale = dw <= dh
-                ? (float) sourceBounds.width() / dw
-                : (float) sourceBounds.height() / dh;
+        final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
+        final Rect crop = mTmpDestinationRect;
+        crop.set(0, 0, destW, destH);
         // Inverse scale for crop to fit in screen coordinates.
-        mTmpDestinationRect.scale(1 / scale);
-        mTmpTransform.setRotate(degrees);
-        mTmpTransform.postScale(scale, scale);
+        crop.scale(1 / scale);
+        crop.offset(insets.left, insets.top);
+        if (isExpanding) {
+            // Expand bounds (shrink insets) in source orientation.
+            positionX -= insets.left * scale;
+            positionY -= insets.top * scale;
+        } else {
+            // Shrink bounds (expand insets) in destination orientation.
+            if (clockwise) {
+                positionX -= insets.top * scale;
+                positionY -= insets.left * scale;
+            } else {
+                positionX += insets.top * scale;
+                positionY += insets.left * scale;
+            }
+        }
+        mTmpTransform.setScale(scale, scale);
+        mTmpTransform.postRotate(degrees);
         mTmpTransform.postTranslate(positionX, positionY);
-        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
-                .setWindowCrop(leash, mTmpDestinationRect.width(), mTmpDestinationRect.height());
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setWindowCrop(leash, crop);
         return this;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e66be66..4ce6c9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.util.RotationUtils.deltaRotation;
+import static android.util.RotationUtils.rotateBounds;
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
@@ -50,8 +52,10 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.Rational;
 import android.view.Display;
@@ -94,6 +98,12 @@
         DisplayController.OnDisplaysChangedListener {
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
     private static final boolean DEBUG = false;
+    /**
+     * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
+     * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
+     * navigation, then the alpha type is unexpected.
+     */
+    private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
 
     // Not a complete set of states but serves what we want right now.
     private enum State {
@@ -127,6 +137,7 @@
         }
     }
 
+    private final Context mContext;
     private final SyncTransactionQueue mSyncTransactionQueue;
     private final PipBoundsState mPipBoundsState;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -160,8 +171,20 @@
         public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
                 PipAnimationController.PipTransitionAnimator animator) {
             final int direction = animator.getTransitionDirection();
-            finishResize(tx, animator.getDestinationBounds(), direction,
-                    animator.getAnimationType());
+            final int animationType = animator.getAnimationType();
+            final Rect destinationBounds = animator.getDestinationBounds();
+            if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
+                    && direction == TRANSITION_DIRECTION_TO_PIP) {
+                // Notify the display to continue the deferred orientation change.
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.scheduleFinishEnterPip(mToken, destinationBounds);
+                mTaskOrganizer.applyTransaction(wct);
+                // The final task bounds will be applied by onFixedRotationFinished so that all
+                // coordinates are in new rotation.
+                mDeferredAnimEndTransaction = tx;
+                return;
+            }
+            finishResize(tx, destinationBounds, direction, animationType);
             sendOnPipTransitionFinished(direction);
             if (direction == TRANSITION_DIRECTION_TO_PIP) {
                 // TODO (b//169221267): Add jank listener for transactions without buffer updates.
@@ -186,10 +209,18 @@
     private SurfaceControl mLeash;
     private State mState = State.UNDEFINED;
     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private long mLastOneShotAlphaAnimationTime;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     private PictureInPictureParams mPictureInPictureParams;
     private IntConsumer mOnDisplayIdChangeCallback;
+    /**
+     * The end transaction of PiP animation for switching between PiP and fullscreen with
+     * orientation change. The transaction should be applied after the display is rotated.
+     */
+    private SurfaceControl.Transaction mDeferredAnimEndTransaction;
+    /** Whether the existing PiP is hidden by alpha. */
+    private boolean mHasFadeOut;
 
     /**
      * If set to {@code true}, the entering animation will be skipped and we will wait for
@@ -203,6 +234,8 @@
      */
     private @Surface.Rotation int mNextRotation;
 
+    private @Surface.Rotation int mCurrentRotation;
+
     /**
      * If set to {@code true}, no entering PiP transition would be kicked off and most likely
      * it's due to the fact that Launcher is handling the transition directly when swiping
@@ -224,6 +257,7 @@
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
+        mContext = context;
         mSyncTransactionQueue = syncTransactionQueue;
         mPipBoundsState = pipBoundsState;
         mPipBoundsAlgorithm = boundsHandler;
@@ -261,10 +295,6 @@
         return mState.isInPip();
     }
 
-    public boolean isDeferringEnterPipAnimation() {
-        return mState.isInPip() && mWaitForFixedRotation;
-    }
-
     /**
      * Returns whether the entry animation is waiting to be started.
      */
@@ -286,6 +316,9 @@
      */
     public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
         mOneShotAnimationType = animationType;
+        if (animationType == ANIM_TYPE_ALPHA) {
+            mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
+        }
     }
 
     /**
@@ -297,9 +330,6 @@
         mInSwipePipToHomeTransition = true;
         sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
         setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
-        // disable the conflicting transaction from fixed rotation, see also
-        // onFixedRotationStarted and onFixedRotationFinished
-        mWaitForFixedRotation = false;
         return mPipBoundsAlgorithm.getEntryDestinationBounds();
     }
 
@@ -355,6 +385,9 @@
                         : WINDOWING_MODE_FULLSCREEN);
         wct.setBounds(mToken, destinationBounds);
         wct.setBoundsChangeTransaction(mToken, tx);
+        // Set the exiting state first so if there is fixed rotation later, the running animation
+        // won't be interrupted by alpha animation for existing PiP.
+        mState = State.EXITING_PIP;
         mSyncTransactionQueue.queue(wct);
         mSyncTransactionQueue.runInSync(t -> {
             // Make sure to grab the latest source hint rect as it could have been
@@ -362,9 +395,8 @@
             final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
                     mPictureInPictureParams, destinationBounds);
             final PipAnimationController.PipTransitionAnimator<?> animator =
-                    scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
-                    0 /* startingAngle */, sourceHintRect, direction,
-                    animationDurationMs, null /* updateBoundsCallback */);
+                    animateResizePip(mPipBoundsState.getBounds(), destinationBounds, sourceHintRect,
+                            direction, animationDurationMs, 0 /* startingAngle */);
             if (animator != null) {
                 // Even though the animation was started above, re-apply the transaction for the
                 // first frame using the SurfaceControl.Transaction supplied by the
@@ -374,7 +406,6 @@
                 // hint during expansion that causes a visible jank/flash. See b/184166183.
                 animator.applySurfaceControlTransaction(mLeash, t, FRACTION_START);
             }
-            mState = State.EXITING_PIP;
         });
     }
 
@@ -447,29 +478,22 @@
         }
 
         if (mInSwipePipToHomeTransition) {
-            final Rect destinationBounds = mPipBoundsState.getBounds();
-            final SurfaceControl.Transaction tx =
-                    mSurfaceControlTransactionFactory.getTransaction();
-            mSurfaceTransactionHelper.resetScale(tx, mLeash, destinationBounds);
-            mSurfaceTransactionHelper.crop(tx, mLeash, destinationBounds);
-            // animation is finished in the Launcher and here we directly apply the final touch.
-            applyEnterPipSyncTransaction(destinationBounds, () -> {
-                // ensure menu's settled in its final bounds first
-                finishResizeForMenu(destinationBounds);
-                sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
-            }, tx);
-            mInSwipePipToHomeTransition = false;
+            if (!mWaitForFixedRotation) {
+                onEndOfSwipePipToHomeTransition();
+            } else {
+                Log.d(TAG, "Defer onTaskAppeared-SwipePipToHome until end of fixed rotation.");
+            }
             return;
         }
 
+        if (mOneShotAnimationType == ANIM_TYPE_ALPHA
+                && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
+                > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
+            Log.d(TAG, "Alpha animation is expired. Use bounds animation.");
+            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+        }
         if (mWaitForFixedRotation) {
-            if (DEBUG) Log.d(TAG, "Defer entering PiP animation, fixed rotation is ongoing");
-            // if deferred, hide the surface till fixed rotation is completed
-            final SurfaceControl.Transaction tx =
-                    mSurfaceControlTransactionFactory.getTransaction();
-            tx.setAlpha(mLeash, 0f);
-            tx.show(mLeash);
-            tx.apply();
+            onTaskAppearedWithFixedRotation();
             return;
         }
 
@@ -500,6 +524,27 @@
         }
     }
 
+    private void onTaskAppearedWithFixedRotation() {
+        if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+            Log.d(TAG, "Defer entering PiP alpha animation, fixed rotation is ongoing");
+            // If deferred, hide the surface till fixed rotation is completed.
+            final SurfaceControl.Transaction tx =
+                    mSurfaceControlTransactionFactory.getTransaction();
+            tx.setAlpha(mLeash, 0f);
+            tx.show(mLeash);
+            tx.apply();
+            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+            return;
+        }
+        final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
+        final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+                mPictureInPictureParams, currentBounds);
+        final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+        animateResizePip(currentBounds, destinationBounds, sourceHintRect,
+                TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, 0 /* startingAngle */);
+        mState = State.ENTERING_PIP;
+    }
+
     /**
      * Called when the display rotation handling is skipped (e.g. when rotation happens while in
      * the middle of an entry transition).
@@ -536,6 +581,20 @@
         }, null /* boundsChangeTransaction */);
     }
 
+    private void onEndOfSwipePipToHomeTransition() {
+        final Rect destinationBounds = mPipBoundsState.getBounds();
+        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+        mSurfaceTransactionHelper.resetScale(tx, mLeash, destinationBounds);
+        mSurfaceTransactionHelper.crop(tx, mLeash, destinationBounds);
+        // The animation is finished in the Launcher and here we directly apply the final touch.
+        applyEnterPipSyncTransaction(destinationBounds, () -> {
+            // Ensure menu's settled in its final bounds first.
+            finishResizeForMenu(destinationBounds);
+            sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+        }, tx);
+        mInSwipePipToHomeTransition = false;
+    }
+
     private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable,
             @Nullable SurfaceControl.Transaction boundsChangeTransaction) {
         // PiP menu is attached late in the process here to avoid any artifacts on the leash
@@ -547,7 +606,6 @@
         if (boundsChangeTransaction != null) {
             wct.setBoundsChangeTransaction(mToken, boundsChangeTransaction);
         }
-        wct.scheduleFinishEnterPip(mToken, destinationBounds);
         mSyncTransactionQueue.queue(wct);
         if (runnable != null) {
             mSyncTransactionQueue.runInSync(t -> runnable.run());
@@ -600,7 +658,7 @@
             Log.wtf(TAG, "Unrecognized token: " + token);
             return;
         }
-        mWaitForFixedRotation = false;
+        clearWaitForFixedRotation();
         mInSwipePipToHomeTransition = false;
         mPictureInPictureParams = null;
         mState = State.UNDEFINED;
@@ -617,8 +675,10 @@
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
-        if (mState != State.ENTERED_PIP) {
+        if (mState != State.ENTERED_PIP && mState != State.EXITING_PIP) {
             Log.d(TAG, "Defer onTaskInfoChange in current state: " + mState);
+            // Defer applying PiP parameters if the task is entering PiP to avoid disturbing
+            // the animation.
             mDeferredTaskInfo = info;
             return;
         }
@@ -648,16 +708,60 @@
     public void onFixedRotationStarted(int displayId, int newRotation) {
         mNextRotation = newRotation;
         mWaitForFixedRotation = true;
+
+        if (mState.isInPip()) {
+            // Fade out the existing PiP to avoid jump cut during seamless rotation.
+            fadeExistingPip(false /* show */);
+        }
     }
 
     @Override
     public void onFixedRotationFinished(int displayId) {
-        if (mWaitForFixedRotation && mState.isInPip()) {
-            final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
-            // schedule a regular animation to ensure all the callbacks are still being sent
-            enterPipWithAlphaAnimation(destinationBounds, 0 /* durationMs */);
+        if (!mWaitForFixedRotation) {
+            return;
         }
+        if (mState == State.TASK_APPEARED) {
+            if (mInSwipePipToHomeTransition) {
+                onEndOfSwipePipToHomeTransition();
+            } else {
+                // Schedule a regular animation to ensure all the callbacks are still being sent.
+                enterPipWithAlphaAnimation(mPipBoundsAlgorithm.getEntryDestinationBounds(),
+                        mEnterAnimationDuration);
+            }
+        } else if (mState == State.ENTERED_PIP && mHasFadeOut) {
+            fadeExistingPip(true /* show */);
+        } else if (mState == State.ENTERING_PIP && mDeferredAnimEndTransaction != null) {
+            final PipAnimationController.PipTransitionAnimator<?> animator =
+                    mPipAnimationController.getCurrentAnimator();
+            final Rect destinationBounds = animator.getDestinationBounds();
+            mPipBoundsState.setBounds(destinationBounds);
+            applyEnterPipSyncTransaction(destinationBounds, () -> {
+                finishResizeForMenu(destinationBounds);
+                sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+            }, mDeferredAnimEndTransaction);
+        }
+        clearWaitForFixedRotation();
+    }
+
+    private void fadeExistingPip(boolean show) {
+        final float alphaStart = show ? 0 : 1;
+        final float alphaEnd = show ? 1 : 0;
+        mPipAnimationController
+                .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), alphaStart, alphaEnd)
+                .setTransitionDirection(TRANSITION_DIRECTION_SAME)
+                .setDuration(show ? mEnterAnimationDuration : mExitAnimationDuration)
+                .start();
+        mHasFadeOut = !show;
+    }
+
+    private void clearWaitForFixedRotation() {
         mWaitForFixedRotation = false;
+        mDeferredAnimEndTransaction = null;
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        mCurrentRotation = newConfig.windowConfiguration.getRotation();
     }
 
     /**
@@ -686,7 +790,11 @@
                 mPipAnimationController.getCurrentAnimator();
         if (animator == null || !animator.isRunning()
                 || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
-            if (mState.isInPip() && fromRotation && !mWaitForFixedRotation) {
+            final boolean rotatingPip = mState.isInPip() && fromRotation;
+            if (rotatingPip && mWaitForFixedRotation && mHasFadeOut) {
+                // The position will be used by fade-in animation when the fixed rotation is done.
+                mPipBoundsState.setBounds(destinationBoundsOut);
+            } else if (rotatingPip) {
                 // Update bounds state to final destination first. It's important to do this
                 // before finishing & cancelling the transition animation so that the MotionHelper
                 // bounds are synchronized to the destination bounds when the animation ends.
@@ -737,7 +845,17 @@
         final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         if (newDestinationBounds.equals(currentDestinationBounds)) return;
         if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
-            animator.updateEndValue(newDestinationBounds);
+            if (mWaitForFixedRotation) {
+                // The new destination bounds are in next rotation (DisplayLayout has been rotated
+                // in computeRotatedBounds). The animation runs in previous rotation so the end
+                // bounds need to be transformed.
+                final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+                final Rect rotatedEndBounds = new Rect(newDestinationBounds);
+                rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
+                animator.updateEndValue(rotatedEndBounds);
+            } else {
+                animator.updateEndValue(newDestinationBounds);
+            }
         }
         animator.setDestinationBounds(newDestinationBounds);
         destinationBoundsOut.set(newDestinationBounds);
@@ -1050,7 +1168,6 @@
             // activity windowing mode set by WM, and set the task bounds to the final bounds
             taskBounds = destinationBounds;
             wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-            wct.scheduleFinishEnterPip(mToken, destinationBounds);
         } else if (isOutPipDirection(direction)) {
             // If we are animating to fullscreen or split screen, then we need to reset the
             // override bounds on the task to ensure that the task "matches" the parent's bounds.
@@ -1096,8 +1213,12 @@
             return null;
         }
         final int rotationDelta = mWaitForFixedRotation
-                ? ((mNextRotation - mPipBoundsState.getDisplayLayout().rotation()) + 4) % 4
+                ? deltaRotation(mCurrentRotation, mNextRotation)
                 : Surface.ROTATION_0;
+        if (rotationDelta != Surface.ROTATION_0) {
+            sourceHintRect = computeRotatedBounds(rotationDelta, direction, destinationBounds,
+                    sourceHintRect);
+        }
         Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
                 ? mPipBoundsState.getBounds() : currentBounds;
         final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
@@ -1107,9 +1228,35 @@
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(durationMs)
                 .start();
+        if (rotationDelta != Surface.ROTATION_0 && direction == TRANSITION_DIRECTION_TO_PIP) {
+            // The destination bounds are used for the end rect of animation and the final bounds
+            // after animation finishes. So after the animation is started, the destination bounds
+            // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
+            // without affecting the animation.
+            animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
+        }
         return animator;
     }
 
+    /** Computes destination bounds in old rotation and returns source hint rect if available. */
+    private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
+            Rect outDestinationBounds, Rect sourceHintRect) {
+        if (direction == TRANSITION_DIRECTION_TO_PIP) {
+            mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), mNextRotation);
+            final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+            outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
+            // Transform the destination bounds to current display coordinates.
+            rotateBounds(outDestinationBounds, displayBounds, mNextRotation, mCurrentRotation);
+        } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+            final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
+            rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
+                    rotationDelta);
+            return PipBoundsAlgorithm.getValidSourceHintRect(mPictureInPictureParams,
+                    rotatedDestinationBounds);
+        }
+        return sourceHintRect;
+    }
+
     /**
      * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
      * screen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index f505e60..b881fea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -114,13 +114,17 @@
      */
     private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
             int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
-        if (!mPipTaskOrganizer.isInPip()
-                || mPipBoundsState.getDisplayLayout().rotation() == toRotation
-                || mPipTaskOrganizer.isDeferringEnterPipAnimation()
-                || mPipTaskOrganizer.isEntryScheduled()) {
-            // Skip if the same rotation has been set or we aren't in PIP or haven't actually
-            // entered PIP yet. We still need to update the display layout in the bounds handler
-            // in this case.
+        if (mPipBoundsState.getDisplayLayout().rotation() == toRotation) {
+            // The same rotation may have been set by auto PiP-able or fixed rotation. So notify
+            // the change with fromRotation=false to apply the rotated destination bounds from
+            // PipTaskOrganizer#onMovementBoundsChanged.
+            updateMovementBounds(null, false /* fromRotation */,
+                    false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
+            return;
+        }
+        if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isEntryScheduled()) {
+            // Update display layout and bounds handler if we aren't in PIP or haven't actually
+            // entered PIP yet.
             onDisplayRotationChangedNotInPip(mContext, toRotation);
             // do not forget to update the movement bounds as well.
             updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index e336287..cb7afc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -15,18 +15,10 @@
  */
 package com.android.wm.shell.startingsurface;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_SAME_PACKAGE;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 
@@ -68,27 +60,24 @@
  */
 public class StartingWindowController implements RemoteCallable<StartingWindowController> {
     private static final String TAG = StartingWindowController.class.getSimpleName();
+
     // TODO b/183150443 Keep this flag open for a while, several things might need to adjust.
-    static final boolean DEBUG_SPLASH_SCREEN = true;
-    static final boolean DEBUG_TASK_SNAPSHOT = false;
+    public static final boolean DEBUG_SPLASH_SCREEN = true;
+    public static final boolean DEBUG_TASK_SNAPSHOT = false;
 
     private final StartingSurfaceDrawer mStartingSurfaceDrawer;
-    private final StartingTypeChecker mStartingTypeChecker = new StartingTypeChecker();
+    private final StartingWindowTypeAlgorithm mStartingWindowTypeAlgorithm;
 
     private BiConsumer<Integer, Integer> mTaskLaunchingCallback;
     private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
     private final Context mContext;
     private final ShellExecutor mSplashScreenExecutor;
 
-    // For Car Launcher
-    public StartingWindowController(Context context, ShellExecutor splashScreenExecutor) {
-        this(context, splashScreenExecutor, new TransactionPool());
-    }
-
     public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
-            TransactionPool pool) {
+            StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, TransactionPool pool) {
         mContext = context;
         mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool);
+        mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm;
         mSplashScreenExecutor = splashScreenExecutor;
     }
 
@@ -109,90 +98,6 @@
         return mSplashScreenExecutor;
     }
 
-    private static class StartingTypeChecker {
-
-        private @StartingWindowInfo.StartingWindowType int
-                estimateStartingWindowType(StartingWindowInfo windowInfo) {
-            final int parameter = windowInfo.startingWindowTypeParameter;
-            final boolean newTask = (parameter & TYPE_PARAMETER_NEW_TASK) != 0;
-            final boolean taskSwitch = (parameter & TYPE_PARAMETER_TASK_SWITCH) != 0;
-            final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0;
-            final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0;
-            final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0;
-            final boolean samePackage = (parameter & TYPE_PARAMETER_SAME_PACKAGE) != 0;
-            return estimateStartingWindowType(windowInfo, newTask, taskSwitch,
-                    processRunning, allowTaskSnapshot, activityCreated, samePackage);
-        }
-
-        // reference from ActivityRecord#getStartingWindowType
-        private int estimateStartingWindowType(StartingWindowInfo windowInfo,
-                boolean newTask, boolean taskSwitch, boolean processRunning,
-                boolean allowTaskSnapshot, boolean activityCreated, boolean samePackage) {
-            if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
-                Slog.d(TAG, "preferredStartingWindowType newTask " + newTask
-                        + " taskSwitch " + taskSwitch
-                        + " processRunning " + processRunning
-                        + " allowTaskSnapshot " + allowTaskSnapshot
-                        + " activityCreated " + activityCreated
-                        + " samePackage " + samePackage);
-            }
-            if (windowInfo.taskInfo.topActivityType != ACTIVITY_TYPE_HOME) {
-                if (!processRunning) {
-                    return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-                }
-                if (newTask) {
-                    if (samePackage) {
-                        return STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
-                    } else {
-                        return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-                    }
-                }
-                if (taskSwitch && !activityCreated) {
-                    return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-                }
-            }
-            if (taskSwitch && allowTaskSnapshot) {
-                final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
-                if (isSnapshotCompatible(windowInfo, snapshot)) {
-                    return STARTING_WINDOW_TYPE_SNAPSHOT;
-                }
-                if (windowInfo.taskInfo.topActivityType != ACTIVITY_TYPE_HOME) {
-                    return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-                }
-            }
-            return STARTING_WINDOW_TYPE_NONE;
-        }
-
-        /**
-         * Returns {@code true} if the task snapshot is compatible with this activity (at least the
-         * rotation must be the same).
-         */
-        private boolean isSnapshotCompatible(StartingWindowInfo windowInfo, TaskSnapshot snapshot) {
-            if (snapshot == null) {
-                if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
-                    Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId);
-                }
-                return false;
-            }
-            if (!snapshot.getTopActivityComponent().equals(windowInfo.taskInfo.topActivity)) {
-                if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
-                    Slog.d(TAG, "isSnapshotCompatible obsoleted snapshot "
-                            + windowInfo.taskInfo.topActivity);
-                }
-                return false;
-            }
-
-            final int taskRotation = windowInfo.taskInfo.configuration
-                    .windowConfiguration.getRotation();
-            final int snapshotRotation = snapshot.getRotation();
-            if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
-                Slog.d(TAG, "isSnapshotCompatible rotation " + taskRotation
-                        + " snapshot " + snapshotRotation);
-            }
-            return taskRotation == snapshotRotation;
-        }
-    }
-
     /*
      * Registers the starting window listener.
      *
@@ -212,7 +117,8 @@
     public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
         mSplashScreenExecutor.execute(() -> {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
-            final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(
+
+            final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
                     windowInfo);
             final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
             if (mTaskLaunchingCallback != null && shouldSendToListener(suggestionType)) {
@@ -228,8 +134,10 @@
                 final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
                 mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
                         snapshot);
+            } else /* suggestionType == STARTING_WINDOW_TYPE_NONE */ {
+                // Don't add a staring window.
             }
-            // If prefer don't show, then don't show!
+
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         });
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowTypeAlgorithm.java
new file mode 100644
index 0000000..de221ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowTypeAlgorithm.java
@@ -0,0 +1,30 @@
+/*
+ * 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.startingsurface;
+
+import android.window.StartingWindowInfo;
+
+/**
+ * Used by {@link StartingWindowController} for determining the type of a new starting window.
+ */
+public interface StartingWindowTypeAlgorithm {
+    /**
+     * @return suggested type for the given window.
+     */
+    @StartingWindowInfo.StartingWindowType
+    int getSuggestedWindowType(StartingWindowInfo windowInfo);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
new file mode 100644
index 0000000..9948e7d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -0,0 +1,123 @@
+/*
+ * 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.startingsurface.phone;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_SAME_PACKAGE;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
+
+import static com.android.wm.shell.startingsurface.StartingWindowController.DEBUG_SPLASH_SCREEN;
+import static com.android.wm.shell.startingsurface.StartingWindowController.DEBUG_TASK_SNAPSHOT;
+
+import android.util.Slog;
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
+
+/**
+ * Algorithm for determining the type of a new starting window on handheld devices.
+ * At the moment also used on Android Auto.
+ */
+public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorithm {
+    private static final String TAG = PhoneStartingWindowTypeAlgorithm.class.getSimpleName();
+
+    @Override
+    public int getSuggestedWindowType(StartingWindowInfo windowInfo) {
+        final int parameter = windowInfo.startingWindowTypeParameter;
+        final boolean newTask = (parameter & TYPE_PARAMETER_NEW_TASK) != 0;
+        final boolean taskSwitch = (parameter & TYPE_PARAMETER_TASK_SWITCH) != 0;
+        final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0;
+        final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0;
+        final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0;
+        final boolean samePackage = (parameter & TYPE_PARAMETER_SAME_PACKAGE) != 0;
+        final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
+
+        if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
+            Slog.d(TAG, "preferredStartingWindowType newTask " + newTask
+                    + " taskSwitch " + taskSwitch
+                    + " processRunning " + processRunning
+                    + " allowTaskSnapshot " + allowTaskSnapshot
+                    + " activityCreated " + activityCreated
+                    + " samePackage " + samePackage
+                    + " topIsHome " + topIsHome);
+        }
+        if (!topIsHome) {
+            if (!processRunning) {
+                return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+            }
+            if (newTask) {
+                if (samePackage) {
+                    return STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
+                } else {
+                    return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+                }
+            }
+            if (taskSwitch && !activityCreated) {
+                return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+            }
+        }
+        if (taskSwitch && allowTaskSnapshot) {
+            if (isSnapshotCompatible(windowInfo)) {
+                return STARTING_WINDOW_TYPE_SNAPSHOT;
+            }
+            if (!topIsHome) {
+                return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+            }
+        }
+        return STARTING_WINDOW_TYPE_NONE;
+    }
+
+
+    /**
+     * Returns {@code true} if the task snapshot is compatible with this activity (at least the
+     * rotation must be the same).
+     */
+    private boolean isSnapshotCompatible(StartingWindowInfo windowInfo) {
+        final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
+        if (snapshot == null) {
+            if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
+                Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId);
+            }
+            return false;
+        }
+        if (!snapshot.getTopActivityComponent().equals(windowInfo.taskInfo.topActivity)) {
+            if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
+                Slog.d(TAG, "isSnapshotCompatible obsoleted snapshot "
+                        + windowInfo.taskInfo.topActivity);
+            }
+            return false;
+        }
+
+        final int taskRotation = windowInfo.taskInfo.configuration
+                .windowConfiguration.getRotation();
+        final int snapshotRotation = snapshot.getRotation();
+        if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
+            Slog.d(TAG, "isSnapshotCompatible rotation " + taskRotation
+                    + " snapshot " + snapshotRotation);
+        }
+        return taskRotation == snapshotRotation;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
new file mode 100644
index 0000000..6e7dec5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/tv/TvStartingWindowTypeAlgorithm.java
@@ -0,0 +1,35 @@
+/*
+ * 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.startingsurface.tv;
+
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
+
+import android.window.StartingWindowInfo;
+
+import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
+
+/**
+ * Algorithm for determining the type of a new starting window on Android TV.
+ * For now we always show empty splash screens on Android TV.
+ */
+public class TvStartingWindowTypeAlgorithm implements StartingWindowTypeAlgorithm {
+    @Override
+    public int getSuggestedWindowType(StartingWindowInfo windowInfo) {
+        // For now we want to always show empty splash screens on TV.
+        return STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 63b9413..882d382 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.util.RotationUtils.rotateBounds;
 import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
@@ -133,17 +134,30 @@
 
     @Test
     public void pipTransitionAnimator_rotatedEndValue() {
+        final DummySurfaceControlTx tx = new DummySurfaceControlTx();
         final Rect startBounds = new Rect(200, 700, 400, 800);
         final Rect endBounds = new Rect(0, 0, 500, 1000);
-        final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+        // Fullscreen to PiP.
+        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
-                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_90);
+                        TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90);
         // Apply fraction 1 to compute the end value.
-        animator.applySurfaceControlTransaction(mLeash, new DummySurfaceControlTx(), 1);
+        animator.applySurfaceControlTransaction(mLeash, tx, 1);
         final Rect rotatedEndBounds = new Rect(endBounds);
         rotateBounds(rotatedEndBounds, endBounds, ROTATION_90);
 
         assertEquals("Expect 90 degree rotated bounds", rotatedEndBounds, animator.mCurrentValue);
+
+        // PiP to fullscreen.
+        startBounds.set(0, 0, 1000, 500);
+        endBounds.set(200, 100, 400, 500);
+        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
+                endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270);
+        animator.applySurfaceControlTransaction(mLeash, tx, 1);
+        rotatedEndBounds.set(endBounds);
+        rotateBounds(rotatedEndBounds, startBounds, ROTATION_270);
+
+        assertEquals("Expect 270 degree rotated bounds", rotatedEndBounds, animator.mCurrentValue);
     }
 
     @Test
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 6560a18..ed447f8 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 androidx.core.os.BuildCompat;
 
+import com.google.android.material.transition.platform.FadeThroughProvider;
 import com.google.android.material.transition.platform.MaterialSharedAxis;
 import com.google.android.material.transition.platform.SlideDistanceProvider;
 
@@ -35,6 +36,7 @@
 
     private static final String TAG = "SettingsTransitionHelper";
     private static final long DURATION = 450L;
+    private static final float FADE_THROUGH_THRESHOLD = 0.22F;
 
     private static MaterialSharedAxis createSettingsSharedAxis(Context context, boolean forward) {
         final MaterialSharedAxis transition = new MaterialSharedAxis(MaterialSharedAxis.X, forward);
@@ -48,12 +50,14 @@
         forwardDistanceProvider.setSlideDistance(distance);
         transition.setDuration(DURATION);
 
+        final FadeThroughProvider fadeThroughProvider =
+                (FadeThroughProvider) transition.getSecondaryAnimatorProvider();
+        fadeThroughProvider.setProgressThreshold(FADE_THROUGH_THRESHOLD);
+
         final Interpolator interpolator =
                 AnimationUtils.loadInterpolator(context, R.interpolator.fast_out_extra_slow_in);
         transition.setInterpolator(interpolator);
 
-        // TODO(b/177480673): Update fade through threshold once (cl/362065364) is released
-
         return transition;
     }
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c0e4df5..8f7f1fa 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -138,6 +138,7 @@
                 new InclusiveIntegerRangeValidator(
                         /* first= */Global.ONE_HANDED_KEYGUARD_SIDE_LEFT,
                         /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
+        VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
     }
 }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 635434b..2f54e21 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -553,6 +553,9 @@
         dumpSetting(s, p,
                 Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
                 GlobalSettingsProto.Development.ENABLE_NON_RESIZABLE_MULTI_WINDOW);
+        dumpSetting(s, p,
+                Settings.Global.DISABLE_WINDOW_BLURS,
+                GlobalSettingsProto.Development.DISABLE_WINDOW_BLURS);
         p.end(developmentToken);
 
         final long deviceToken = p.start(GlobalSettingsProto.DEVICE);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 150d10d..22e38f4 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -231,6 +231,7 @@
                     Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR,
                     Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
                     Settings.Global.DEVICE_DEMO_MODE,
+                    Settings.Global.DISABLE_WINDOW_BLURS,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
                     Settings.Global.BATTERY_TIP_CONSTANTS,
                     Settings.Global.DEFAULT_SM_DP_PLUS,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 4142e51..b75252b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -58,7 +58,6 @@
     /** Returns true if the gesture should be rejected. */
     boolean isFalseTouch(int interactionType);
 
-
     /**
      * Does basic checking to see if gesture looks like a tap.
      *
@@ -127,8 +126,19 @@
     /** Removes a {@link FalsingBeliefListener}. */
     void removeFalsingBeliefListener(FalsingBeliefListener listener);
 
+    /** Adds a {@link FalsingTapListener}. */
+    void addTapListener(FalsingTapListener falsingTapListener);
+
+    /** Removes a {@link FalsingTapListener}. */
+    void removeTapListener(FalsingTapListener falsingTapListener);
+
     /** Listener that is alerted when falsing belief level crosses a predfined threshold. */
     interface FalsingBeliefListener {
         void onFalse();
     }
+
+    /** Listener that is alerted when a double tap is required to confirm a single tap. */
+    interface FalsingTapListener {
+        void onDoubleTapRequired();
+    }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index fffcafb..4d4c909 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.plugins.qs;
 
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -57,7 +56,6 @@
     void setQsExpansion(float qsExpansionFraction, float headerTranslation);
     void setHeaderListening(boolean listening);
     void notifyCustomizeChanged();
-
     void setContainer(ViewGroup container);
     void setExpandClickListener(OnClickListener onClickListener);
 
@@ -75,6 +73,16 @@
         return isShowingDetail();
     }
 
+    /**
+     * If QS should translate as we pull it down, or if it should be static.
+     */
+    void setTranslateWhileExpanding(boolean shouldTranslate);
+
+    /**
+     * A rounded corner clipping that makes QS feel as if it were behind everything.
+     */
+    void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible);
+
     @ProvidesInterface(version = HeightListener.VERSION)
     interface HeightListener {
         int VERSION = 1;
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 1125092..ea644cf 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -24,7 +24,6 @@
     android:clipChildren="true"
     android:clipToPadding="true"
     android:orientation="vertical"
-    android:background="?android:attr/colorBackground"
     android:paddingStart="12dp">
 
     <!-- Package Info -->
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 12c864c..bea50e8 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -43,7 +43,7 @@
                    android:visibility="invisible" />
     </com.android.systemui.statusbar.BackDropView>
 
-    <com.android.systemui.statusbar.ScrimView
+    <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -51,7 +51,7 @@
         sysui:ignoreRightInset="true"
         />
 
-    <com.android.systemui.statusbar.ScrimView
+    <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_notifications"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -72,7 +72,7 @@
 
     <include layout="@layout/brightness_mirror_container" />
 
-    <com.android.systemui.statusbar.ScrimView
+    <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_in_front"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 4e0204b..22d934e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.TransitionOldType;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
+import android.annotation.SuppressLint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -241,10 +242,16 @@
 
                 final Runnable animationFinishedCallback = new Runnable() {
                     @Override
+                    @SuppressLint("NewApi")
                     public void run() {
                         try {
                             counterLauncher.cleanUp(info.getRootLeash());
                             counterWallpaper.cleanUp(info.getRootLeash());
+                            // Release surface references now. This is apparently to free GPU
+                            // memory while doing quick operations (eg. during CTS).
+                            for (int i = 0; i < info.getChanges().size(); ++i) {
+                                info.getChanges().get(i).getLeash().release();
+                            }
                             finishCallback.onTransitionFinished(null /* wct */);
                         } catch (RemoteException e) {
                             Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 7d1c0a6..351dfd5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -24,6 +24,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.Parcelable;
@@ -127,7 +128,7 @@
                 }
                 t.apply();
                 final RecentsAnimationControllerCompat wrapControl =
-                        new RecentsControllerWrap(controller, finishedCallback, pausingTask);
+                        new RecentsControllerWrap(controller, info, finishedCallback, pausingTask);
                 recents.onAnimationStart(wrapControl, apps, wallpapers, new Rect(0, 0, 0, 0),
                         new Rect());
             }
@@ -161,10 +162,12 @@
         private final RecentsAnimationControllerCompat mWrapped;
         private final IRemoteTransitionFinishedCallback mFinishCB;
         private final WindowContainerToken mPausingTask;
+        private final TransitionInfo mInfo;
 
-        RecentsControllerWrap(RecentsAnimationControllerCompat wrapped,
+        RecentsControllerWrap(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
                 IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask) {
             mWrapped = wrapped;
+            mInfo = info;
             mFinishCB = finishCB;
             mPausingTask = pausingTask;
         }
@@ -192,7 +195,9 @@
             }
         }
 
-        @Override public void finish(boolean toHome, boolean sendUserLeaveHint) {
+        @Override
+        @SuppressLint("NewApi")
+        public void finish(boolean toHome, boolean sendUserLeaveHint) {
             try {
                 if (!toHome && mPausingTask != null) {
                     // The gesture went back to opening the app rather than continuing with
@@ -207,6 +212,11 @@
                 Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
             }
             if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint);
+            // Release surface references now. This is apparently to free GPU
+            // memory while doing quick operations (eg. during CTS).
+            for (int i = 0; i < mInfo.getChanges().size(); ++i) {
+                mInfo.getChanges().get(i).getLeash().release();
+            }
         }
 
         @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index fc80dbe..588f4bb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import android.graphics.Rect;
 import android.os.UserHandle;
 import android.util.Slog;
 
@@ -48,6 +49,7 @@
     private final ConfigurationController mConfigurationController;
     private final DozeParameters mDozeParameters;
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+    private final Rect mClipBounds = new Rect();
 
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
@@ -299,4 +301,17 @@
             mView.updateLogoutView(shouldShowLogout());
         }
     };
+
+    /**
+     * Rect that specifies how KSV should be clipped, on its parent's coordinates.
+     */
+    public void setClipBounds(Rect clipBounds) {
+        if (clipBounds != null) {
+            mClipBounds.set(clipBounds.left, (int) (clipBounds.top - mView.getY()),
+                    clipBounds.right, (int) (clipBounds.bottom - mView.getY()));
+            mView.setClipBounds(mClipBounds);
+        } else {
+            mView.setClipBounds(null);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
index 8765c9a..947466f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
@@ -1,7 +1,3 @@
 set noparent
 
-kchyn@google.com
-jaggies@google.com
-curtislb@google.com
-ilyamaty@google.com
-joshmccloskey@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 01d5959..9e621b3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -79,6 +79,7 @@
 
     private final Collection<FalsingClassifier> mClassifiers;
     private final List<FalsingBeliefListener> mFalsingBeliefListeners = new ArrayList<>();
+    private List<FalsingTapListener> mFalsingTapListeners = new ArrayList<>();
 
     private final SessionListener mSessionListener = new SessionListener() {
         @Override
@@ -277,6 +278,7 @@
                         FalsingClassifier.Result.falsed(
                                 0, getClass().getSimpleName(), "bad history"));
                 logDebug("False Single Tap: true (bad history)");
+                mFalsingTapListeners.forEach(FalsingTapListener::onDoubleTapRequired);
                 return true;
             } else {
                 mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(0.1));
@@ -356,6 +358,16 @@
     }
 
     @Override
+    public void addTapListener(FalsingTapListener listener) {
+        mFalsingTapListeners.add(listener);
+    }
+
+    @Override
+    public void removeTapListener(FalsingTapListener listener) {
+        mFalsingTapListeners.remove(listener);
+    }
+
+    @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.println("BRIGHTLINE FALSING MANAGER");
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index e557773..e8445d4 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -146,4 +146,14 @@
     public void removeFalsingBeliefListener(FalsingBeliefListener listener) {
         mFalsingBeliefListeners.remove(listener);
     }
+
+    @Override
+    public void addTapListener(FalsingTapListener falsingTapListener) {
+
+    }
+
+    @Override
+    public void removeTapListener(FalsingTapListener falsingTapListener) {
+
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 1723291..6b819fb 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -176,6 +176,16 @@
     }
 
     @Override
+    public void addTapListener(FalsingTapListener listener) {
+        mInternalFalsingManager.addTapListener(listener);
+    }
+
+    @Override
+    public void removeTapListener(FalsingTapListener listener) {
+        mInternalFalsingManager.removeTapListener(listener);
+    }
+
+    @Override
     public void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
         mInternalFalsingManager.onProximityEvent(proximityEvent);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index d8ade2b..4196465 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -376,8 +376,8 @@
             case REASON_SENSOR_DOUBLE_TAP: return "doubletap";
             case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress";
             case PULSE_REASON_DOCKING: return "docking";
-            case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakelockscreen";
-            case REASON_SENSOR_WAKE_UP: return "wakeup";
+            case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "reach-wakelockscreen";
+            case REASON_SENSOR_WAKE_UP: return "presence-wakeup";
             case REASON_SENSOR_TAP: return "tap";
             case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps";
             case REASON_SENSOR_QUICK_PICKUP: return "quickPickup";
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 5cea31b..39adabb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -40,11 +40,9 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.internal.logging.nano.MetricsProto;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.plugins.SensorManagerPlugin;
@@ -491,10 +489,6 @@
             mHandler.post(mWakeLock.wrap(() -> {
                 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
                 if (mSensor != null && mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
-                    int subType = (int) event.values[0];
-                    MetricsLogger.action(
-                            mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE,
-                            subType);
                     UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index ee55965..c45eb35 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -24,7 +24,6 @@
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.metrics.LogMaker;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.Formatter;
@@ -33,12 +32,8 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -70,8 +65,6 @@
     /** adb shell am broadcast -a com.android.systemui.doze.pulse com.android.systemui */
     private static final String PULSE_ACTION = "com.android.systemui.doze.pulse";
 
-    private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
-
     /**
      * Last value sent by the wake-display sensor.
      * Assuming that the screen should start on.
@@ -99,12 +92,11 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final AuthController mAuthController;
     private final DelayableExecutor mMainExecutor;
+    private final UiEventLogger mUiEventLogger;
 
     private long mNotificationPulseTime;
     private boolean mPulsePending;
 
-    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
-
     /** see {@link #onProximityFar} prox for callback */
     private boolean mWantProxSensor;
     private boolean mWantTouchScreenSensors;
@@ -143,7 +135,10 @@
         DOZING_UPDATE_AUTH_TRIGGERED(657),
 
         @UiEvent(doc = "Dozing updated because quick pickup sensor woke up.")
-        DOZING_UPDATE_QUICK_PICKUP(708);
+        DOZING_UPDATE_QUICK_PICKUP(708),
+
+        @UiEvent(doc = "Dozing updated - sensor wakeup timed out (from quick pickup or presence)")
+        DOZING_UPDATE_WAKE_TIMEOUT(794);
 
         private final int mId;
 
@@ -182,7 +177,8 @@
             ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck,
             DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
             SecureSettings secureSettings, AuthController authController,
-            @Main DelayableExecutor mainExecutor) {
+            @Main DelayableExecutor mainExecutor,
+            UiEventLogger uiEventLogger) {
         mContext = context;
         mDozeHost = dozeHost;
         mConfig = config;
@@ -200,6 +196,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mAuthController = authController;
         mMainExecutor = mainExecutor;
+        mUiEventLogger = uiEventLogger;
     }
 
     @Override
@@ -328,11 +325,8 @@
 
     private void gentleWakeUp(int reason) {
         // Log screen wake up reason (lift/pickup, tap, double-tap)
-        mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING)
-                .setType(MetricsEvent.TYPE_UPDATE)
-                .setSubtype(reason));
         Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
-                .ifPresent(UI_EVENT_LOGGER::log);
+                .ifPresent(mUiEventLogger::log);
         if (mDozeParameters.getDisplayNeedsBlanking()) {
             // Let's prepare the display to wake-up by drawing black.
             // This will cover the hardware wake-up sequence, where the display
@@ -401,10 +395,9 @@
                 }
                 if (state == DozeMachine.State.DOZE) {
                     mMachine.requestState(DozeMachine.State.DOZE_AOD);
-                    // Logs AOD open due to sensor wake up.
-                    mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING)
-                            .setType(MetricsEvent.TYPE_OPEN)
-                            .setSubtype(reason));
+                    // Log sensor triggered
+                    Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
+                            .ifPresent(mUiEventLogger::log);
 
                     if (isQuickPickup) {
                         // schedule runnable to go back to DOZE
@@ -427,10 +420,8 @@
                     return;
                 }
                 mMachine.requestState(DozeMachine.State.DOZE);
-                // Logs AOD close due to sensor wake up.
-                mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING)
-                        .setType(MetricsEvent.TYPE_CLOSE)
-                        .setSubtype(reason));
+                // log wake timeout
+                mUiEventLogger.log(DozingUpdateUiEvent.DOZING_UPDATE_WAKE_TIMEOUT);
             }
         }
     }
@@ -563,10 +554,8 @@
         }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
 
         // Logs request pulse reason on AOD screen.
-        mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING)
-                .setType(MetricsEvent.TYPE_UPDATE).setSubtype(reason));
         Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
-                .ifPresent(UI_EVENT_LOGGER::log);
+                .ifPresent(mUiEventLogger::log);
     }
 
     private boolean canPulse() {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index e44e305..19edeb8 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -98,7 +98,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
-import com.android.internal.colorextraction.drawable.ScrimDrawable;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -117,6 +116,7 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 767d7ab..411e0f0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -30,11 +30,11 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
-import com.android.internal.colorextraction.drawable.ScrimDrawable;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.ScrimController;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 495461e..94cb2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -54,6 +54,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.util.animation.TransitionLayout;
 
@@ -519,7 +520,8 @@
             mediaCoverImageView.setImageIcon(recommendation.getIcon());
 
             // Set up the click listener if applicable.
-            setSmartspaceOnClickListener(mediaCoverImageView, recommendation, callback);
+            setSmartspaceOnClickListener(mediaCoverImageView, recommendation,
+                    target.getSmartspaceTargetId(), callback);
 
             setVisibleAndAlpha(expandedSet, mediaCoverItemsResIds.get(i), true);
             setVisibleAndAlpha(expandedSet, mediaLogoItemsResIds.get(i), true);
@@ -614,6 +616,7 @@
     private void setSmartspaceOnClickListener(
             @NonNull View view,
             @NonNull SmartspaceAction action,
+            @NonNull String targetId,
             @Nullable View.OnClickListener callback) {
         if (view == null || action == null || action.getIntent() == null) {
             Log.e(TAG, "No tap action can be set up");
@@ -621,6 +624,16 @@
         }
 
         view.setOnClickListener(v -> {
+            // When media recommendation card is shown, there could be only one card.
+            SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+                    760, // SMARTSPACE_CARD_CLICK
+                    targetId.hashCode(),
+                    SysUiStatsLog
+                            .SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
+                    getSurfaceForSmartspaceLogging(mMediaViewController.getCurrentEndLocation()),
+                    /* rank */ 1,
+                    /* cardinality */ 1);
+
             mActivityStarter.postStartActivityDismissingKeyguard(
                     action.getIntent(),
                     0 /* delay */,
@@ -630,4 +643,14 @@
             }
         });
     }
+
+    private int getSurfaceForSmartspaceLogging(int currentEndLocation) {
+        if (currentEndLocation == MediaHierarchyManager.LOCATION_QQS
+                || currentEndLocation == MediaHierarchyManager.LOCATION_QS) {
+            return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE;
+        } else if (currentEndLocation == MediaHierarchyManager.LOCATION_LOCKSCREEN) {
+            return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN;
+        }
+        return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 0ed96ee..6ef29d6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -104,7 +104,12 @@
     /**
      * Set from the notification and used as fallback when PlaybackState cannot be determined
      */
-    val isClearable: Boolean = true
+    val isClearable: Boolean = true,
+
+    /**
+     * Timestamp when this player was last active.
+     */
+    var lastActive: Long = 0L
 )
 
 /** State of a media action. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index a274eab..5f73ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media
 
 import android.app.smartspace.SmartspaceTarget
+import android.os.SystemProperties
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -24,14 +25,23 @@
 import com.android.systemui.settings.CurrentUserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
 private const val TAG = "MediaDataFilter"
 private const val DEBUG = true
 
 /**
+ * Maximum age of a media control to re-activate on smartspace signal. If there is no media control
+ * available within this time window, smartspace recommendations will be shown instead.
+ */
+private val SMARTSPACE_MAX_AGE = SystemProperties
+        .getLong("debug.sysui.smartspace_max_age", TimeUnit.HOURS.toMillis(3))
+
+/**
  * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
- * switches (removing entries for the previous user, adding back entries for the current user)
+ * switches (removing entries for the previous user, adding back entries for the current user). Also
+ * filters out smartspace updates in favor of local recent media, when avaialble.
  *
  * This is added at the end of the pipeline since we may still need to handle callbacks from
  * background users (e.g. timeouts).
@@ -52,6 +62,7 @@
     // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
     private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
     private var hasSmartspace: Boolean = false
+    private var reactivatedKey: String? = null
 
     init {
         userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -86,6 +97,30 @@
 
     override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
         hasSmartspace = true
+
+        // Before forwarding the smartspace target, first check if we have recently inactive media
+        val now = System.currentTimeMillis()
+        val sorted = userEntries.toSortedMap(compareBy {
+            userEntries.get(it)?.lastActive ?: -1
+        })
+        if (sorted.size > 0) {
+            val lastActiveKey = sorted.lastKey() // most recently active
+            val timeSinceActive = sorted.get(lastActiveKey)?.let {
+                now - it.lastActive
+            } ?: Long.MAX_VALUE
+            if (timeSinceActive < SMARTSPACE_MAX_AGE) {
+                // Notify listeners to consider this media active
+                Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
+                reactivatedKey = lastActiveKey
+                val mediaData = sorted.get(lastActiveKey)!!.copy(active = true)
+                listeners.forEach {
+                    it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData)
+                }
+                return
+            }
+        }
+
+        // If no recent media, continue with smartspace update
         listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data) }
     }
 
@@ -101,6 +136,21 @@
 
     override fun onSmartspaceMediaDataRemoved(key: String) {
         hasSmartspace = false
+
+        // First check if we had reactivated media instead of forwarding smartspace
+        reactivatedKey?.let {
+            val lastActiveKey = it
+            reactivatedKey = null
+            Log.d(TAG, "expiring reactivated key $lastActiveKey")
+            // Notify listeners to update with actual active value
+            userEntries.get(lastActiveKey)?.let { mediaData ->
+                listeners.forEach {
+                    it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData)
+                }
+            }
+            return
+        }
+
         listeners.forEach { it.onSmartspaceMediaDataRemoved(key) }
     }
 
@@ -137,7 +187,8 @@
         if (DEBUG) Log.d(TAG, "Media carousel swiped away")
         val mediaKeys = userEntries.keys.toSet()
         mediaKeys.forEach {
-            mediaDataManager.setTimedOut(it, timedOut = true)
+            // Force updates to listeners, needed for re-activated card
+            mediaDataManager.setTimedOut(it, timedOut = true, forceUpdate = true)
         }
         if (hasSmartspace) {
             mediaDataManager.dismissSmartspaceRecommendation()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 5ba04a0..7807176 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -394,9 +394,9 @@
      * This will make the player not active anymore, hiding it from QQS and Keyguard.
      * @see MediaData.active
      */
-    internal fun setTimedOut(token: String, timedOut: Boolean) {
+    internal fun setTimedOut(token: String, timedOut: Boolean, forceUpdate: Boolean = false) {
         mediaEntries[token]?.let {
-            if (it.active == !timedOut) {
+            if (it.active == !timedOut && !forceUpdate) {
                 return
             }
             it.active = !timedOut
@@ -470,12 +470,13 @@
         }
 
         val mediaAction = getResumeMediaAction(resumeAction)
+        val lastActive = System.currentTimeMillis()
         foregroundExecutor.execute {
             onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
                     null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
                     packageName, token, appIntent, device = null, active = false,
                     resumeAction = resumeAction, resumption = true, notificationKey = packageName,
-                    hasCheckedForResume = true))
+                    hasCheckedForResume = true, lastActive = lastActive))
         }
     }
 
@@ -586,9 +587,9 @@
         }
 
         val isLocalSession = mediaController.playbackInfo?.playbackType ==
-            MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL ?: true
+            MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
         val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
-
+        val lastActive = System.currentTimeMillis()
         foregroundExecutor.execute {
             val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
             val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -598,7 +599,8 @@
                     actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
                     active, resumeAction = resumeAction, isLocalSession = isLocalSession,
                     notificationKey = key, hasCheckedForResume = hasCheckedForResume,
-                    isPlaying = isPlaying, isClearable = sbn.isClearable()))
+                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
+                    lastActive = lastActive))
         }
     }
 
@@ -724,7 +726,7 @@
         Assert.isMainThread()
         val removed = mediaEntries.remove(key)
         if (useMediaResumption && removed?.resumeAction != null &&
-                !isBlockedFromResume(removed.packageName)) {
+                !isBlockedFromResume(removed.packageName) && removed?.isLocalSession == true) {
             Log.d(TAG, "Not removing $key because resumable")
             // Move to resume key (aka package name) if that key doesn't already exist.
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 80d1371..b0be576 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -173,7 +173,7 @@
             mediaBrowser?.disconnect()
             // If we don't have a resume action, check if we haven't already
             if (data.resumeAction == null && !data.hasCheckedForResume &&
-                    !blockedApps.contains(data.packageName)) {
+                    !blockedApps.contains(data.packageName) && data.isLocalSession) {
                 // TODO also check for a media button receiver intended for restarting (b/154127084)
                 Log.d(TAG, "Checking for service component for " + data.packageName)
                 val pm = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 7cfe4c4..e848e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -70,11 +70,10 @@
      * finished
      */
     @MediaLocation
-    private var currentEndLocation: Int = -1
+    var currentEndLocation: Int = -1
 
     /**
-     * The ending location of the view where it ends when all animations and transitions have
-     * finished
+     * The starting location of the view where it starts for all animations and transitions
      */
     @MediaLocation
     private var currentStartLocation: Int = -1
diff --git a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
index 68a829c..0efef02 100644
--- a/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/NotificationHelper.java
@@ -42,7 +42,7 @@
 /** Helper functions to handle notifications in People Tiles. */
 public class NotificationHelper {
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
-    private static final String TAG = "PeopleNotificationHelper";
+    private static final String TAG = "PeopleNotifHelper";
 
     /** Returns the notification with highest priority to be shown in People Tiles. */
     public static NotificationEntry getHighestPriorityNotification(
@@ -209,5 +209,30 @@
         }
         return null;
     }
+
+    /** Returns whether {@code notification} is a group conversation. */
+    private static boolean isGroupConversation(Notification notification) {
+        return notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION, false);
+    }
+
+    /**
+     * Returns {@code message}'s sender's name if {@code notification} is from a group conversation.
+     */
+    public static CharSequence getSenderIfGroupConversation(Notification notification,
+            Notification.MessagingStyle.Message message) {
+        if (!isGroupConversation(notification)) {
+            if (DEBUG) {
+                Log.d(TAG, "Notification is not from a group conversation, not checking sender.");
+            }
+            return null;
+        }
+        Person person = message.getSenderPerson();
+        if (person == null) {
+            if (DEBUG) Log.d(TAG, "Notification from group conversation doesn't include sender.");
+            return null;
+        }
+        if (DEBUG) Log.d(TAG, "Returning sender from group conversation notification.");
+        return person.getName();
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index eefe5ca..99a17ffb 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.people.NotificationHelper.getContactUri;
 import static com.android.systemui.people.NotificationHelper.getMessagingStyleMessages;
+import static com.android.systemui.people.NotificationHelper.getSenderIfGroupConversation;
 import static com.android.systemui.people.NotificationHelper.hasReadContactsPermission;
 import static com.android.systemui.people.NotificationHelper.isMissedCall;
 import static com.android.systemui.people.NotificationHelper.shouldMatchNotificationByUri;
@@ -233,6 +234,7 @@
                 // Reset notification content.
                 .setNotificationKey(null)
                 .setNotificationContent(null)
+                .setNotificationSender(null)
                 .setNotificationDataUri(null)
                 .setMessagesCount(0)
                 // Reset missed calls category.
@@ -245,9 +247,9 @@
      * {@code messagesCount}.
      */
     public static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile,
-            NotificationEntry notificationEntry, int messagesCount) {
+            PeopleTileKey key, NotificationEntry notificationEntry, int messagesCount) {
         if (notificationEntry == null || notificationEntry.getSbn().getNotification() == null) {
-            if (DEBUG) Log.d(TAG, "Notification is null");
+            if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification is null");
             return removeNotificationFields(tile);
         }
         Notification notification = notificationEntry.getSbn().getNotification();
@@ -256,7 +258,7 @@
                 getMessagingStyleMessages(notification);
 
         if (!isMissedCall && ArrayUtils.isEmpty(messages)) {
-            if (DEBUG) Log.d(TAG, "Notification has no content");
+            if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification has no content");
             return removeNotificationFields(tile);
         }
 
@@ -268,13 +270,18 @@
         CharSequence content = (isMissedCall && !hasMessageText)
                 ? context.getString(R.string.missed_call) : message.getText();
         Uri dataUri = message != null ? message.getDataUri() : null;
-        if (DEBUG) Log.d(TAG, "Notification message has text: " + hasMessageText);
+        if (DEBUG) {
+            Log.d(TAG, "Tile key: " + key.toString() + ". Notification message has text: "
+                    + hasMessageText);
+        }
+        CharSequence sender = getSenderIfGroupConversation(notification, message);
 
         return tile
                 .toBuilder()
                 .setNotificationKey(notificationEntry.getSbn().getKey())
                 .setNotificationCategory(notification.category)
                 .setNotificationContent(content)
+                .setNotificationSender(sender)
                 .setNotificationDataUri(dataUri)
                 .setMessagesCount(messagesCount)
                 .build();
@@ -459,7 +466,7 @@
         }
         if (DEBUG) {
             Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName() + ", "
-                    + tile.getPackageName());
+                    + tile.getPackageName() + ". Updating app widget view.");
         }
         RemoteViews views = new PeopleTileViewHelper(context, tile, appWidgetId,
                 options).getViews();
@@ -472,7 +479,9 @@
     public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
             Context context, int appWidgetId, PeopleSpaceTile tile) {
         if (tile == null) {
-            Log.d(TAG, "Tile is null, skipping storage and update.");
+            if (DEBUG) {
+                Log.w(TAG, "Widget: " + appWidgetId + "Tile is null, skipping storage and update.");
+            }
             return;
         }
         Bundle options = AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile);
@@ -483,7 +492,10 @@
     public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
             Context context, int appWidgetId, Optional<PeopleSpaceTile> optionalTile) {
         if (!optionalTile.isPresent()) {
-            Log.d(TAG, "Tile is null, skipping storage and update.");
+            if (DEBUG) {
+                Log.w(TAG, "Widget: " + appWidgetId
+                        + "Optional tile is not present, skipping storage and update.");
+            }
             return;
         }
         updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, optionalTile.get());
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index bc9a45b..d4ddc65 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -59,6 +59,7 @@
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
+import com.android.systemui.people.widget.PeopleTileKey;
 
 import java.text.NumberFormat;
 import java.time.Duration;
@@ -148,6 +149,8 @@
      * content, then birthdays, then the most recent status, and finally last interaction.
      */
     private RemoteViews getViewForTile() {
+        PeopleTileKey key = new PeopleTileKey(mTile);
+        if (DEBUG) Log.d(TAG, "Creating view for tile key: " + key.toString());
         if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
             if (DEBUG) Log.d(TAG, "Create missed call view");
             return createMissedCallRemoteViews();
@@ -179,7 +182,7 @@
         return createLastInteractionRemoteViews();
     }
 
-    private void setMaxLines(RemoteViews views) {
+    private void setMaxLines(RemoteViews views, boolean showSender) {
         int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp(
                 R.dimen.content_text_size_for_medium)
                 : getSizeInDp(R.dimen.content_text_size_for_medium);
@@ -187,6 +190,9 @@
         int notificationContentHeight = getContentHeightForLayout(lineHeight);
         int maxAdaptiveLines = Math.floorDiv(notificationContentHeight, lineHeight);
         int maxLines = Math.max(MIN_CONTENT_MAX_LINES, maxAdaptiveLines);
+
+        // Save a line for sender's name, if present.
+        if (showSender) maxLines--;
         views.setInt(R.id.text_content, "setMaxLines", maxLines);
     }
 
@@ -350,7 +356,7 @@
         RemoteViews views = getViewForContentLayout();
         views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
         views.setViewVisibility(R.id.messages_count, View.GONE);
-        setMaxLines(views);
+        setMaxLines(views, false);
         views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
         views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
         return views;
@@ -358,6 +364,7 @@
 
     private RemoteViews createNotificationRemoteViews() {
         RemoteViews views = getViewForContentLayout();
+        CharSequence sender = mTile.getNotificationSender();
         Uri image = mTile.getNotificationDataUri();
         if (image != null) {
             // TODO: Use NotificationInlineImageCache
@@ -366,7 +373,7 @@
             views.setViewVisibility(R.id.text_content, View.GONE);
             views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
         } else {
-            setMaxLines(views);
+            setMaxLines(views, !TextUtils.isEmpty(sender));
             CharSequence content = mTile.getNotificationContent();
             views = setPunctuationRemoteViewsFields(views, content);
             views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorPrimary);
@@ -382,9 +389,12 @@
                 views.setViewVisibility(R.id.predefined_icon, View.GONE);
             }
         }
-        // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and
-        //  subtract 1 from maxLines when present.
-        views.setViewVisibility(R.id.subtext, View.GONE);
+        if (!TextUtils.isEmpty(sender)) {
+            views.setViewVisibility(R.id.subtext, View.VISIBLE);
+            views.setTextViewText(R.id.subtext, sender);
+        } else {
+            views.setViewVisibility(R.id.subtext, View.GONE);
+        }
         return views;
     }
 
@@ -414,7 +424,7 @@
         }
         views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
         views.setViewVisibility(R.id.messages_count, View.GONE);
-        setMaxLines(views);
+        setMaxLines(views, false);
         // Secondary text color for statuses.
         views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorSecondary);
         views.setTextViewText(R.id.text_content, statusText);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
index 7254eec..73c43eb 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
@@ -41,7 +41,7 @@
             PeopleSpaceTile tile) {
         Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
         if (tile == null) {
-            if (DEBUG) Log.d(TAG, "Requested to store null tile");
+            if (DEBUG) Log.w(TAG, "Requested to store null tile");
             return options;
         }
         options.putParcelable(OPTIONS_PEOPLE_TILE, tile);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index f11c1bd..64a6509 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -236,7 +236,7 @@
     @Nullable
     public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key) {
         if (!key.isValid()) {
-            Log.e(TAG, "PeopleTileKey invalid: " + key);
+            Log.e(TAG, "PeopleTileKey invalid: " + key.toString());
             return null;
         }
 
@@ -267,7 +267,14 @@
      */
     public void updateWidgetsWithNotificationChanged(StatusBarNotification sbn,
             PeopleSpaceUtils.NotificationAction notificationAction) {
-        if (DEBUG) Log.d(TAG, "updateWidgetsWithNotificationChanged called");
+        if (DEBUG) {
+            Log.d(TAG, "updateWidgetsWithNotificationChanged called");
+            if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) {
+                Log.d(TAG, "Notification posted, key: " + sbn.getKey());
+            } else {
+                Log.d(TAG, "Notification removed, key: " + sbn.getKey());
+            }
+        }
         if (mIsForTesting) {
             updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction);
             return;
@@ -282,7 +289,7 @@
             PeopleTileKey key = new PeopleTileKey(
                     sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
             if (!key.isValid()) {
-                Log.d(TAG, "Invalid key from sbn");
+                Log.d(TAG, "Sbn doesn't contain valid PeopleTileKey: " + key.toString());
                 return;
             }
             int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
@@ -309,8 +316,12 @@
 
     /** Updates {@code widgetIdsToUpdate} with {@code action}. */
     private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate) {
-        Log.d(TAG, "Fetching grouped notifications");
+        if (widgetIdsToUpdate.isEmpty()) {
+            if (DEBUG) Log.d(TAG, "No widgets to update, returning.");
+            return;
+        }
         try {
+            if (DEBUG) Log.d(TAG, "Fetching grouped notifications");
             Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
                     getGroupedConversationNotifications();
 
@@ -331,14 +342,15 @@
      * Augments {@code tile} based on notifications returned from {@code notificationEntryManager}.
      */
     public PeopleSpaceTile augmentTileFromNotificationEntryManager(PeopleSpaceTile tile) {
-        Log.d(TAG, "Augmenting tile from NotificationEntryManager widget: " + tile.getId());
+        PeopleTileKey key = new PeopleTileKey(tile);
+        Log.d(TAG, "Augmenting tile from NotificationEntryManager widget: " + key.toString());
         Map<PeopleTileKey, Set<NotificationEntry>> notifications =
                 getGroupedConversationNotifications();
         String contactUri = null;
         if (tile.getContactUri() != null) {
             contactUri = tile.getContactUri().toString();
         }
-        return augmentTileFromNotifications(tile, contactUri, notifications);
+        return augmentTileFromNotifications(tile, key, contactUri, notifications);
     }
 
     /** Returns active and pending notifications grouped by {@link PeopleTileKey}. */
@@ -367,9 +379,9 @@
     }
 
     /** Augments {@code tile} based on {@code notifications}, matching {@code contactUri}. */
-    public PeopleSpaceTile augmentTileFromNotifications(PeopleSpaceTile tile, String contactUri,
-            Map<PeopleTileKey, Set<NotificationEntry>> notifications) {
-        if (DEBUG) Log.d(TAG, "Augmenting tile from notifications. Tile id: " + tile.getId());
+    public PeopleSpaceTile augmentTileFromNotifications(PeopleSpaceTile tile, PeopleTileKey key,
+            String contactUri, Map<PeopleTileKey, Set<NotificationEntry>> notifications) {
+        if (DEBUG) Log.d(TAG, "Augmenting tile from notifications. Tile key: " + key.toString());
         boolean hasReadContactsPermission =  mPackageManager.checkPermission(READ_CONTACTS,
                 tile.getPackageName()) == PackageManager.PERMISSION_GRANTED;
 
@@ -384,13 +396,12 @@
             }
         }
 
-        PeopleTileKey key = new PeopleTileKey(tile);
         Set<NotificationEntry> allNotifications = notifications.get(key);
         if (allNotifications == null) {
             allNotifications = new HashSet<>();
         }
         if (allNotifications.isEmpty() && notificationsByUri.isEmpty()) {
-            if (DEBUG) Log.d(TAG, "No existing notifications for tile: " + key);
+            if (DEBUG) Log.d(TAG, "No existing notifications for tile: " + key.toString());
             return removeNotificationFields(tile);
         }
 
@@ -402,22 +413,28 @@
         NotificationEntry highestPriority = getHighestPriorityNotification(allNotifications);
 
         if (DEBUG) Log.d(TAG, "Augmenting tile from notification, key: " + key.toString());
-        return augmentTileFromNotification(mContext, tile, highestPriority, messagesCount);
+        return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount);
     }
 
     /** Returns an augmented tile for an existing widget. */
     @Nullable
     public Optional<PeopleSpaceTile> getAugmentedTileForExistingWidget(int widgetId,
             Map<PeopleTileKey, Set<NotificationEntry>> notifications) {
-        Log.d(TAG, "Augmenting tile for widget: " + widgetId);
+        Log.d(TAG, "Augmenting tile for existing widget: " + widgetId);
         PeopleSpaceTile tile = getTileForExistingWidget(widgetId);
         if (tile == null) {
+            if (DEBUG) {
+                Log.w(TAG, "Widget: " + widgetId
+                        + ". Null tile for existing widget, skipping update.");
+            }
             return Optional.empty();
         }
         String contactUriString = mSharedPrefs.getString(String.valueOf(widgetId), null);
         // Should never be null, but using ofNullable for extra safety.
+        PeopleTileKey key = new PeopleTileKey(tile);
+        if (DEBUG) Log.d(TAG, "Existing widget: " + widgetId + ". Tile key: " + key.toString());
         return Optional.ofNullable(
-                augmentTileFromNotifications(tile, contactUriString, notifications));
+                augmentTileFromNotifications(tile, key, contactUriString, notifications));
     }
 
     /** Returns stored widgets for the conversation specified. */
@@ -644,12 +661,12 @@
         }
 
         synchronized (mLock) {
-            if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getId());
+            if (DEBUG) Log.d(TAG, "Add storage for : " + key.toString());
             PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
                     tile.getContactUri());
         }
         try {
-            if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
+            if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + key.toString());
             mLauncherApps.cacheShortcuts(tile.getPackageName(),
                     Collections.singletonList(tile.getId()),
                     tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
@@ -679,7 +696,7 @@
                 if (DEBUG) Log.d(TAG, "Already registered listener");
                 return;
             }
-            if (DEBUG) Log.d(TAG, "Register listener for " + widgetId + " with " + key);
+            if (DEBUG) Log.d(TAG, "Register listener for " + widgetId + " with " + key.toString());
             mListeners.put(key, newListener);
         }
         mPeopleManager.registerConversationListener(key.getPackageName(),
@@ -750,7 +767,9 @@
                 if (DEBUG) Log.d(TAG, "Cannot find listener to unregister");
                 return;
             }
-            if (DEBUG) Log.d(TAG, "Unregister listener for " + appWidgetId + " with " + key);
+            if (DEBUG) {
+                Log.d(TAG, "Unregister listener for " + appWidgetId + " with " + key.toString());
+            }
             mListeners.remove(key);
         }
         mPeopleManager.unregisterConversationListener(registeredListener);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
index 309b32f..cd36091 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java
@@ -29,6 +29,7 @@
 
     private final int mTouchSlop;
     private float mDownY;
+    private boolean mScrollEnabled = true;
 
     public NonInterceptingScrollView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -85,6 +86,16 @@
         return super.onInterceptTouchEvent(ev);
     }
 
+    @Override
+    public boolean canScrollVertically(int direction) {
+        return mScrollEnabled && super.canScrollVertically(direction);
+    }
+
+    @Override
+    public boolean canScrollHorizontally(int direction) {
+        return mScrollEnabled && super.canScrollHorizontally(direction);
+    }
+
     public int getScrollRange() {
         int scrollRange = 0;
         if (getChildCount() > 0) {
@@ -94,4 +105,12 @@
         }
         return scrollRange;
     }
+
+    /**
+     * Enable scrolling for this view. Needed because the view might be clipped but still intercepts
+     * touches on the lockscreen.
+     */
+    public void setScrollingEnabled(boolean enabled) {
+        mScrollEnabled = enabled;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index cefcd4a..294d765 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -54,6 +54,7 @@
     private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
 
     public static final float EXPANDED_TILE_DELAY = .86f;
+    public static final float SHORT_PARALLAX_AMOUNT = 0.1f;
     private static final long QQS_FADE_IN_DURATION = 200L;
     // Fade out faster than fade in to finish before QQS hides.
     private static final long QQS_FADE_OUT_DURATION = 50L;
@@ -101,6 +102,7 @@
     private final Executor mExecutor;
     private final TunerService mTunerService;
     private boolean mShowCollapsedOnKeyguard;
+    private boolean mTranslateWhileExpanding;
 
     @Inject
     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
@@ -242,6 +244,9 @@
         int width = mQs.getView() != null ? mQs.getView().getMeasuredWidth() : 0;
         int heightDiff = height - mQs.getHeader().getBottom()
                 + mQs.getHeader().getPaddingBottom();
+        if (!mTranslateWhileExpanding) {
+            heightDiff *= SHORT_PARALLAX_AMOUNT;
+        }
         firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0);
 
         int qqsTileHeight = 0;
@@ -570,6 +575,13 @@
         setCurrentPosition();
     };
 
+    /**
+     * True whe QS will be pulled from the top, false when it will be clipped.
+     */
+    public void setTranslateWhileExpanding(boolean shouldTranslate) {
+        mTranslateWhileExpanding = shouldTranslate;
+    }
+
     static class HeightExpansionAnimator {
         private final List<View> mViews = new ArrayList<>();
         private final ValueAnimator mAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 6b09e2e..e89803d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -20,6 +20,8 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.util.AttributeSet;
 import android.view.View;
@@ -54,6 +56,10 @@
     private static final PhysicsAnimator.SpringConfig BACKGROUND_SPRING
             = new PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_MEDIUM,
             SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+    private int mFancyClippingTop;
+    private int mFancyClippingBottom;
+    private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
+    private  final Path mFancyClippingPath = new Path();
     private int mBackgroundBottom = -1;
     private int mHeightOverride = -1;
     private View mQSDetail;
@@ -70,6 +76,7 @@
     private int mContentPadding = -1;
     private boolean mAnimateBottomOnNextLayout;
     private int mNavBarInset = 0;
+    private boolean mClippingEnabled;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -169,6 +176,15 @@
                 MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
     }
 
+    @Override
+    public void dispatchDraw(Canvas canvas) {
+        if (!mFancyClippingPath.isEmpty()) {
+            canvas.translate(0, -getTranslationY());
+            canvas.clipOutPath(mFancyClippingPath);
+            canvas.translate(0, getTranslationY());
+        }
+        super.dispatchDraw(canvas);
+    }
 
     @Override
     protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
@@ -187,6 +203,7 @@
         super.onLayout(changed, left, top, right, bottom);
         updateExpansion(mAnimateBottomOnNextLayout /* animate */);
         mAnimateBottomOnNextLayout = false;
+        updateClippingPath();
     }
 
     public void disable(int state1, int state2, boolean animate) {
@@ -281,6 +298,7 @@
 
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
+        mQSPanelContainer.setScrollingEnabled(expansion > 0.0f);
         updateExpansion();
     }
 
@@ -318,4 +336,46 @@
         }
         return mSizePoint.y;
     }
+
+    /**
+     * Clip QS bottom using a concave shape.
+     */
+    public void setFancyClipping(int top, int bottom, int radius, boolean enabled) {
+        boolean updatePath = false;
+        if (mFancyClippingRadii[0] != radius) {
+            mFancyClippingRadii[0] = radius;
+            mFancyClippingRadii[1] = radius;
+            mFancyClippingRadii[2] = radius;
+            mFancyClippingRadii[3] = radius;
+            updatePath = true;
+        }
+        if (mFancyClippingTop != top) {
+            mFancyClippingTop = top;
+            updatePath = true;
+        }
+        if (mFancyClippingBottom != bottom) {
+            mFancyClippingBottom = bottom;
+            updatePath = true;
+        }
+        if (mClippingEnabled != enabled) {
+            mClippingEnabled = enabled;
+            updatePath = true;
+        }
+
+        if (updatePath) {
+            updateClippingPath();
+        }
+    }
+
+    private void updateClippingPath() {
+        mFancyClippingPath.reset();
+        if (!mClippingEnabled) {
+            invalidate();
+            return;
+        }
+
+        mFancyClippingPath.addRoundRect(0, mFancyClippingTop, getWidth(),
+                mFancyClippingBottom, mFancyClippingRadii, Path.Direction.CW);
+        invalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b95194a..d5cb777 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -107,6 +107,11 @@
     private QuickQSPanelController mQuickQSPanelController;
     private QSCustomizerController mQSCustomizerController;
     private FeatureFlags mFeatureFlags;
+    /**
+     * When true, QS will translate from outside the screen. It will be clipped with parallax
+     * otherwise.
+     */
+    private boolean mTranslateWhileExpanding;
 
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -254,6 +259,13 @@
         }
     }
 
+    @Override
+    public void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible) {
+        if (getView() instanceof QSContainerImpl) {
+            ((QSContainerImpl) getView()).setFancyClipping(top, bottom, cornerRadius, visible);
+        }
+    }
+
     private void setEditLocation(View view) {
         View edit = view.findViewById(android.R.id.edit);
         int[] loc = edit.getLocationOnScreen();
@@ -394,16 +406,23 @@
     }
 
     @Override
+    public void setTranslateWhileExpanding(boolean shouldTranslate) {
+        mTranslateWhileExpanding = shouldTranslate;
+        mQSAnimator.setTranslateWhileExpanding(shouldTranslate);
+    }
+
+    @Override
     public void setQsExpansion(float expansion, float headerTranslation) {
         if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
 
         if (mQSAnimator != null) {
             final boolean showQSOnLockscreen = expansion > 0;
-            final boolean showQSUnlocked = headerTranslation == 0;
+            final boolean showQSUnlocked = headerTranslation == 0 || !mTranslateWhileExpanding;
             mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked);
         }
         mContainer.setExpansion(expansion);
-        final float translationScaleY = expansion - 1;
+        final float translationScaleY = (mTranslateWhileExpanding
+                ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
         boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard;
         if (!mHeaderAnimating && !headerWillBeAnimating()) {
             getView().setTranslationY(
diff --git a/core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
similarity index 77%
rename from core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java
rename to packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index 1fc126e..0a55fbe 100644
--- a/core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.colorextraction.drawable;
+package com.android.systemui.scrim;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -49,6 +49,7 @@
     private float mCornerRadius;
     private Rect mBounds;
     private ConcaveInfo mConcaveInfo;
+    private int mBottomEdgePosition;
 
     public ScrimDrawable() {
         mPaint = new Paint();
@@ -143,21 +144,43 @@
      * Make bottom edge concave with provided corner radius
      */
     public void setBottomEdgeConcave(float radius) {
+        if (radius == 0) {
+            // Disable clipping completely when there's no radius.
+            mConcaveInfo = null;
+            return;
+        }
         // only rounding top corners for clip out path
         float[] cornerRadii = new float[]{radius, radius, radius, radius, 0, 0, 0, 0};
         mConcaveInfo = new ConcaveInfo(radius, cornerRadii);
     }
 
+    /**
+     * Location of concave edge.
+     * @see #setBottomEdgeConcave(float)
+     */
+    public void setBottomEdgePosition(int y) {
+        if (mBottomEdgePosition == y) {
+            return;
+        }
+        mBottomEdgePosition = y;
+        if (mConcaveInfo == null) {
+            return;
+        }
+        updatePath();
+        invalidateSelf();
+    }
+
     @Override
     public void draw(@NonNull Canvas canvas) {
         mPaint.setColor(mMainColor);
         mPaint.setAlpha(mAlpha);
         if (mConcaveInfo != null) {
             drawConcave(canvas);
+        } else {
+            canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
+                    getBounds().bottom + mCornerRadius,
+                    /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);
         }
-        canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
-                getBounds().bottom + mCornerRadius,
-                /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);
     }
 
     private void drawConcave(Canvas canvas) {
@@ -165,19 +188,23 @@
         if (mBounds == null
                 || getBounds().right != mBounds.right
                 || getBounds().left != mBounds.left) {
-            mConcaveInfo.mPath.reset();
-            float left = getBounds().left;
-            float right = getBounds().right;
-            float top = 0f;
-            float bottom = mConcaveInfo.mPathOverlap;
-            mConcaveInfo.mPath.addRoundRect(left, top, right, bottom,
-                    mConcaveInfo.mCornerRadii, Path.Direction.CW);
+            mBounds = getBounds();
+            updatePath();
         }
-        mBounds = getBounds();
-        int translation = (int) (mBounds.bottom - mConcaveInfo.mPathOverlap);
-        canvas.translate(0, translation);
         canvas.clipOutPath(mConcaveInfo.mPath);
-        canvas.translate(0, -translation);
+        canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
+                mBottomEdgePosition + mConcaveInfo.mPathOverlap, mPaint);
+    }
+
+    private void updatePath() {
+        mConcaveInfo.mPath.reset();
+        if (mBounds == null) {
+            mBounds = getBounds();
+        }
+        float top = mBottomEdgePosition;
+        float bottom = mBottomEdgePosition + mConcaveInfo.mPathOverlap;
+        mConcaveInfo.mPath.addRoundRect(mBounds.left, top, mBounds.right, bottom,
+                mConcaveInfo.mCornerRadii, Path.Direction.CW);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
rename to packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index a537299..0d9ade6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.scrim;
 
 import static java.lang.Float.isNaN;
 
@@ -38,7 +38,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.drawable.ScrimDrawable;
 import com.android.systemui.R;
 
 import java.util.concurrent.Executor;
@@ -96,6 +95,9 @@
         });
     }
 
+    /**
+     * Needed for WM Shell, which has its own thread structure.
+     */
     public void setExecutor(Executor executor, Looper looper) {
         mExecutor = executor;
         mExecutorLooper = looper;
@@ -108,7 +110,8 @@
         }
     }
 
-    public void setDrawable(Drawable drawable) {
+    @VisibleForTesting
+    void setDrawable(Drawable drawable) {
         executeOnExecutor(() -> {
             mDrawable = drawable;
             mDrawable.setCallback(this);
@@ -144,16 +147,24 @@
         });
     }
 
+    /**
+     * Sets the color of the scrim, without animating them.
+     */
     public void setColors(@NonNull ColorExtractor.GradientColors colors) {
         setColors(colors, false);
     }
 
+    /**
+     * Sets the scrim colors, optionally animating them.
+     * @param colors The colors.
+     * @param animated If we should animate the transition.
+     */
     public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) {
         if (colors == null) {
             throw new IllegalArgumentException("Colors cannot be null");
         }
         executeOnExecutor(() -> {
-            synchronized(mColorLock) {
+            synchronized (mColorLock) {
                 if (mColors.equals(colors)) {
                     return;
                 }
@@ -168,17 +179,28 @@
         return mDrawable;
     }
 
+    /**
+     * Returns current scrim colors.
+     */
     public ColorExtractor.GradientColors getColors() {
-        synchronized(mColorLock) {
+        synchronized (mColorLock) {
             mTmpColors.set(mColors);
         }
         return mTmpColors;
     }
 
+    /**
+     * Applies tint to this view, without animations.
+     */
     public void setTint(int color) {
         setTint(color, false);
     }
 
+    /**
+     * Tints this view, optionally animating it.
+     * @param color The color.
+     * @param animated If we should animate.
+     */
     public void setTint(int color, boolean animated) {
         executeOnExecutor(() -> {
             if (mTintColor == color) {
@@ -200,8 +222,8 @@
         } else {
             boolean hasAlpha = Color.alpha(mTintColor) != 0;
             if (hasAlpha) {
-                PorterDuff.Mode targetMode = mColorFilter == null ? Mode.SRC_OVER :
-                    mColorFilter.getMode();
+                PorterDuff.Mode targetMode = mColorFilter == null
+                        ? Mode.SRC_OVER : mColorFilter.getMode();
                 if (mColorFilter == null || mColorFilter.getColor() != mTintColor) {
                     mColorFilter = new PorterDuffColorFilter(mTintColor, targetMode);
                 }
@@ -254,6 +276,9 @@
         return mViewAlpha;
     }
 
+    /**
+     * Sets a callback that is invoked whenever the alpha, color, or tint change.
+     */
     public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) {
         mChangeRunnable = changeRunnable;
         mChangeRunnableExecutor = changeRunnableExecutor;
@@ -276,9 +301,9 @@
      * Make bottom edge concave so overlap between layers is not visible for alphas between 0 and 1
      * @return height of concavity
      */
-    public float enableBottomEdgeConcave() {
+    public float enableBottomEdgeConcave(boolean clipScrim) {
         if (mDrawable instanceof ScrimDrawable) {
-            float radius = getResources().getDimensionPixelSize(CORNER_RADIUS);
+            float radius = clipScrim ? getResources().getDimensionPixelSize(CORNER_RADIUS) : 0;
             ((ScrimDrawable) mDrawable).setBottomEdgeConcave(radius);
             return radius;
         }
@@ -286,6 +311,16 @@
     }
 
     /**
+     * The position of the bottom of the scrim, used for clipping.
+     * @see #enableBottomEdgeConcave(boolean)
+     */
+    public void setBottomEdgePosition(int y) {
+        if (mDrawable instanceof ScrimDrawable) {
+            ((ScrimDrawable) mDrawable).setBottomEdgePosition(y);
+        }
+    }
+
+    /**
      * Enable view to have rounded corners with radius of {@link #CORNER_RADIUS}
      */
     public void enableRoundedCorners() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 357256c..1ad253e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -24,7 +24,9 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -64,17 +66,11 @@
 
     private static final Uri BRIGHTNESS_MODE_URI =
             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE);
-    private static final Uri BRIGHTNESS_URI =
-            Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
-    private static final Uri BRIGHTNESS_FLOAT_URI =
-            Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT);
     private static final Uri BRIGHTNESS_FOR_VR_FLOAT_URI =
             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT);
 
-    private final float mDefaultBacklight;
     private final float mMinimumBacklightForVr;
     private final float mMaximumBacklightForVr;
-    private final float mDefaultBacklightForVr;
 
     private final int mDisplayId;
     private final Context mContext;
@@ -86,6 +82,20 @@
     private final Handler mBackgroundHandler;
     private final BrightnessObserver mBrightnessObserver;
 
+    private final DisplayListener mDisplayListener = new DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {}
+
+        @Override
+        public void onDisplayRemoved(int displayId) {}
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            mBackgroundHandler.post(mUpdateSliderRunnable);
+            notifyCallbacks();
+        }
+    };
+
     private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
             new ArrayList<BrightnessStateChangeCallback>();
 
@@ -94,6 +104,8 @@
     private boolean mListening;
     private boolean mExternalChange;
     private boolean mControlValueInitialized;
+    private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN;
+    private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX;
 
     private ValueAnimator mSliderAnimator;
 
@@ -110,28 +122,19 @@
         }
 
         @Override
-        public void onChange(boolean selfChange) {
-            onChange(selfChange, null);
-        }
-
-        @Override
         public void onChange(boolean selfChange, Uri uri) {
             if (selfChange) return;
 
             if (BRIGHTNESS_MODE_URI.equals(uri)) {
                 mBackgroundHandler.post(mUpdateModeRunnable);
                 mBackgroundHandler.post(mUpdateSliderRunnable);
-            } else if (BRIGHTNESS_FLOAT_URI.equals(uri)) {
-                mBackgroundHandler.post(mUpdateSliderRunnable);
             } else if (BRIGHTNESS_FOR_VR_FLOAT_URI.equals(uri)) {
                 mBackgroundHandler.post(mUpdateSliderRunnable);
             } else {
                 mBackgroundHandler.post(mUpdateModeRunnable);
                 mBackgroundHandler.post(mUpdateSliderRunnable);
             }
-            for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
-                cb.onBrightnessLevelChanged();
-            }
+            notifyCallbacks();
         }
 
         public void startObserving() {
@@ -141,19 +144,16 @@
                     BRIGHTNESS_MODE_URI,
                     false, this, UserHandle.USER_ALL);
             cr.registerContentObserver(
-                    BRIGHTNESS_URI,
-                    false, this, UserHandle.USER_ALL);
-            cr.registerContentObserver(
-                    BRIGHTNESS_FLOAT_URI,
-                    false, this, UserHandle.USER_ALL);
-            cr.registerContentObserver(
                     BRIGHTNESS_FOR_VR_FLOAT_URI,
                     false, this, UserHandle.USER_ALL);
+            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler,
+                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
         public void stopObserving() {
             final ContentResolver cr = mContext.getContentResolver();
             cr.unregisterContentObserver(this);
+            mDisplayManager.unregisterDisplayListener(mDisplayListener);
         }
 
     }
@@ -233,11 +233,15 @@
     private final Runnable mUpdateSliderRunnable = new Runnable() {
         @Override
         public void run() {
-            final float valFloat;
             final boolean inVrMode = mIsVrModeEnabled;
-            valFloat = mDisplayManager.getBrightness(mDisplayId);
+            final BrightnessInfo info = mContext.getDisplay().getBrightnessInfo();
+            if (info == null) {
+                return;
+            }
+            mBrightnessMax = info.brightnessMaximum;
+            mBrightnessMin = info.brightnessMinimum;
             // Value is passed as intbits, since this is what the message takes.
-            final int valueAsIntBits = Float.floatToIntBits(valFloat);
+            final int valueAsIntBits = Float.floatToIntBits(info.brightness);
             mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
                     inVrMode ? 1 : 0).sendToTarget();
         }
@@ -295,13 +299,10 @@
 
         mDisplayId = mContext.getDisplayId();
         PowerManager pm = context.getSystemService(PowerManager.class);
-        mDefaultBacklight = mContext.getDisplay().getBrightnessDefault();
         mMinimumBacklightForVr = pm.getBrightnessConstraint(
                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR);
         mMaximumBacklightForVr = pm.getBrightnessConstraint(
                 PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR);
-        mDefaultBacklightForVr = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR);
 
         mDisplayManager = context.getSystemService(DisplayManager.class);
         mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
@@ -337,7 +338,6 @@
         final float minBacklight;
         final float maxBacklight;
         final int metric;
-        final String settingToChange;
 
         if (mIsVrModeEnabled) {
             metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR;
@@ -347,12 +347,12 @@
             metric = mAutomatic
                     ? MetricsEvent.ACTION_BRIGHTNESS_AUTO
                     : MetricsEvent.ACTION_BRIGHTNESS;
-            minBacklight = PowerManager.BRIGHTNESS_MIN;
-            maxBacklight = PowerManager.BRIGHTNESS_MAX;
+            minBacklight = mBrightnessMin;
+            maxBacklight = mBrightnessMax;
         }
-        final float valFloat = MathUtils.min(convertGammaToLinearFloat(value,
-                minBacklight, maxBacklight),
-                1.0f);
+        final float valFloat = MathUtils.min(
+                convertGammaToLinearFloat(value, minBacklight, maxBacklight),
+                maxBacklight);
         if (stopTracking) {
             // TODO(brightnessfloat): change to use float value instead.
             MetricsLogger.action(mContext, metric,
@@ -403,8 +403,8 @@
             min = mMinimumBacklightForVr;
             max = mMaximumBacklightForVr;
         } else {
-            min = PowerManager.BRIGHTNESS_MIN;
-            max = PowerManager.BRIGHTNESS_MAX;
+            min = mBrightnessMin;
+            max = mBrightnessMax;
         }
         // convertGammaToLinearFloat returns 0-1
         if (BrightnessSynchronizer.floatEquals(brightnessValue,
@@ -439,6 +439,13 @@
         mSliderAnimator.start();
     }
 
+    private void notifyCallbacks() {
+        final int size = mChangeCallbacks.size();
+        for (int i = 0; i < size; i++) {
+            mChangeCallbacks.get(i).onBrightnessLevelChanged();
+        }
+    }
+
     /** Factory for creating a {@link BrightnessController}. */
     public static class Factory {
         private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 4fc49ed..36a370c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -780,7 +780,7 @@
         return mView.isLayoutRtl();
     }
 
-    public float getLeft() {
+    public int getLeft() {
         return  mView.getLeft();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index a4ee9ee..5399094 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -411,13 +411,22 @@
                 Trace.endSection();
                 break;
             case MODE_UNLOCK_COLLAPSING:
-            case MODE_SHOW_BOUNCER:
-                Trace.beginSection("MODE_UNLOCK_COLLAPSING or MODE_SHOW_BOUNCER");
+                Trace.beginSection("MODE_UNLOCK_COLLAPSING");
                 if (!wasDeviceInteractive) {
                     mPendingShowBouncer = true;
                 } else {
                     showBouncer();
-                    mKeyguardViewController.notifyKeyguardAuthenticated(false /* strongAuth */);
+                    mKeyguardViewController.notifyKeyguardAuthenticated(
+                            false /* strongAuth */);
+                }
+                Trace.endSection();
+                break;
+            case MODE_SHOW_BOUNCER:
+                Trace.beginSection("MODE_SHOW_BOUNCER");
+                if (!wasDeviceInteractive) {
+                    mPendingShowBouncer = true;
+                } else {
+                    showBouncer();
                 }
                 Trace.endSection();
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 961699e..9449836 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -53,6 +53,7 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.UserManager;
+import android.os.VibrationEffect;
 import android.service.quickaccesswallet.QuickAccessWalletClient;
 import android.util.Log;
 import android.util.MathUtils;
@@ -101,6 +102,7 @@
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -206,6 +208,7 @@
     private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
     private final BiometricUnlockController mBiometricUnlockController;
     private final NotificationPanelView mView;
+    private final VibratorHelper mVibratorHelper;
     private final MetricsLogger mMetricsLogger;
     private final ActivityManager mActivityManager;
     private final ConfigurationController mConfigurationController;
@@ -515,6 +518,7 @@
     private boolean mDelayShowingKeyguardStatusBar;
 
     private boolean mAnimatingQS;
+    private final Rect mKeyguardStatusAreaClipBounds = new Rect();
     private int mOldLayoutDirection;
     private NotificationShelfController mNotificationShelfController;
 
@@ -522,6 +526,7 @@
     private final Executor mUiExecutor;
 
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+    private int mScrimCornerRadius;
 
     private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
         @Override
@@ -543,6 +548,14 @@
         }
     };
 
+    private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
+        @Override
+        public void onDoubleTapRequired() {
+            showTransientIndication(R.string.notification_tap_again);
+            mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        }
+    };
+
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
             @Main Resources resources,
@@ -590,6 +603,7 @@
                 statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(),
                 statusBarTouchableRegionManager, ambientState);
         mView = view;
+        mVibratorHelper = vibratorHelper;
         mMetricsLogger = metricsLogger;
         mActivityManager = activityManager;
         mConfigurationController = configurationController;
@@ -628,6 +642,7 @@
         mDozeParameters = dozeParameters;
         mBiometricUnlockController = biometricUnlockController;
         mScrimController = scrimController;
+        mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
         mUserManager = userManager;
         mMediaDataManager = mediaDataManager;
         mQuickAccessWalletClient = quickAccessWalletClient;
@@ -852,10 +867,16 @@
     public void updateResources() {
         mSplitShadeNotificationsTopPadding =
                 mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade);
+        mScrimCornerRadius =
+                mResources.getDimensionPixelSize(R.dimen.notification_scrim_corner_radius);
         int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
         int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
         mShouldUseSplitNotificationShade =
                 Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources);
+        mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
+        if (mQs != null) {
+            mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+        }
         // To change the constraints at runtime, all children of the ConstraintLayout must have ids
         ensureAllViewsHaveIds(mNotificationContainerParent);
         ConstraintSet constraintSet = new ConstraintSet();
@@ -2003,15 +2024,23 @@
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
     }
 
-    private void setNotificationBounds(float qsExpansionFraction, int qsPanelBottomY) {
-        float top = 0;
-        float bottom = 0;
-        float left = 0;
-        float right = 0;
-        if (qsPanelBottomY > 0) {
-            // notification shade is expanding/expanded
+    /**
+     * Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade
+     * and QS state.
+     *
+     * @param qsFraction QS expansion fraction, from getQsExpansionFraction().
+     * @param qsPanelBottomY Absolute y position of the bottom of QS as it's being pulled.
+     */
+    private void setNotificationBounds(float qsFraction, int qsPanelBottomY) {
+        int top = 0;
+        int bottom = 0;
+        int left = 0;
+        int right = 0;
+        boolean visible = qsFraction > 0 || qsPanelBottomY > 0;
+        if (visible || !mShouldUseSplitNotificationShade) {
             if (!mShouldUseSplitNotificationShade) {
-                top = qsPanelBottomY;
+                float notificationTop = mAmbientState.getStackY() - mQsNotificationTopPadding;
+                top = (int) Math.min(qsPanelBottomY, notificationTop);
                 bottom = getView().getBottom();
                 left = getView().getLeft();
                 right = getView().getRight();
@@ -2022,6 +2051,17 @@
                 right = mNotificationStackScrollLayoutController.getRight();
             }
         }
+
+        if (!mShouldUseSplitNotificationShade) {
+            // Fancy clipping for quick settings
+            if (mQs != null) {
+                mQs.setFancyClipping(top, bottom, mScrimCornerRadius, visible);
+            }
+            // The padding on this area is large enough that we can use a cheaper clipping strategy
+            mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
+            mKeyguardStatusViewController.setClipBounds(visible
+                    ? mKeyguardStatusAreaClipBounds : null);
+        }
         mScrimController.setNotificationsBounds(left, top, right, bottom);
     }
 
@@ -2493,6 +2533,10 @@
         float appearAmount = mNotificationStackScrollLayoutController
                 .calculateAppearFraction(mExpandedHeight);
         float startHeight = -mQsExpansionHeight;
+        if (!mShouldUseSplitNotificationShade && mBarState == StatusBarState.SHADE) {
+            // Small parallax as we pull down and clip QS
+            startHeight = -mQsExpansionHeight * 0.2f;
+        }
         if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
                 && mNotificationStackScrollLayoutController.isPulseExpanding()) {
             if (!mPulseExpansionHandler.isExpanding()
@@ -3128,8 +3172,10 @@
             mQs.setPanelView(mHeightListener);
             mQs.setExpandClickListener(mOnClickListener);
             mQs.setHeaderClickable(mQsExpansionEnabled);
+            mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
             updateQSPulseExpansion();
             mQs.setOverscrolling(mStackScrollerOverscrolling);
+            mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
 
             // recompute internal state when qspanel height changes
             mQs.getView().addOnLayoutChangeListener(
@@ -3925,7 +3971,10 @@
                 // animate out
                 // the top of QS
                 if (!mQsExpanded) {
-                    mQs.animateHeaderSlidingOut();
+                    // TODO(b/185683835) Nicer clipping when using new spacial model
+                    if (mShouldUseSplitNotificationShade) {
+                        mQs.animateHeaderSlidingOut();
+                    }
                 }
             } else {
                 mKeyguardStatusBar.setAlpha(1f);
@@ -3978,6 +4027,7 @@
             // window, so
             // force a call to onThemeChanged
             mConfigurationListener.onThemeChanged();
+            mFalsingManager.addTapListener(mFalsingTapListener);
         }
 
         @Override
@@ -3986,6 +4036,7 @@
             mStatusBarStateController.removeCallback(mStatusBarStateListener);
             mConfigurationController.removeCallback(mConfigurationListener);
             mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+            mFalsingManager.removeTapListener(mFalsingTapListener);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 064086a..5a2a6f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -147,9 +147,6 @@
     private float mInitialTouchX;
     private boolean mTouchDisabled;
 
-    // AmbientState will never be null since it provides an @Inject constructor for Dagger to call.
-    private AmbientState mAmbientState;
-
     /**
      * Whether or not the PanelView can be expanded or collapsed with a drag.
      */
@@ -172,6 +169,7 @@
     protected final Resources mResources;
     protected final KeyguardStateController mKeyguardStateController;
     protected final SysuiStatusBarStateController mStatusBarStateController;
+    protected final AmbientState mAmbientState;
 
     protected void onExpandingFinished() {
         mBar.onExpandingFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5e9c758..0d96ead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -48,8 +48,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -96,6 +96,7 @@
      * When at least 1 scrim is fully opaque (alpha set to 1.)
      */
     public static final int OPAQUE = 2;
+    private boolean mClipsQsScrim;
 
     @IntDef(prefix = {"VISIBILITY_"}, value = {
             TRANSPARENT,
@@ -165,6 +166,7 @@
     // Assuming the shade is expanded during initialization
     private float mExpansionFraction = 1f;
     private float mQsExpansion;
+    private boolean mQsBottomVisible;
 
     private boolean mDarkenWhileDragging;
     private boolean mExpansionAffectsAlpha = true;
@@ -183,6 +185,7 @@
 
     private int mInFrontTint;
     private int mBehindTint;
+    private int mNotificationsTint;
     private int mBubbleTint;
 
     private boolean mWallpaperVisibilityTimedOut;
@@ -265,6 +268,7 @@
         mScrimForBubble = scrimForBubble;
         updateThemeColors();
 
+        behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
         mNotificationsScrim.enableRoundedCorners();
 
         if (mScrimBehindChangeRunnable != null) {
@@ -331,14 +335,17 @@
 
         mInFrontTint = state.getFrontTint();
         mBehindTint = state.getBehindTint();
+        mNotificationsTint = state.getNotifTint();
         mBubbleTint = state.getBubbleTint();
 
         mInFrontAlpha = state.getFrontAlpha();
         mBehindAlpha = state.getBehindAlpha();
         mBubbleAlpha = state.getBubbleAlpha();
-        if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) {
+        mNotificationsAlpha = state.getNotifAlpha();
+        if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
             throw new IllegalStateException("Scrim opacity is NaN for state: " + state + ", front: "
-                    + mInFrontAlpha + ", back: " + mBehindAlpha);
+                    + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
+                    + mNotificationsAlpha);
         }
         applyExpansionToAlpha();
 
@@ -397,7 +404,7 @@
             scheduleUpdate();
         }
 
-        dispatchScrimState(mScrimBehind.getViewAlpha());
+        dispatchBackScrimState(mScrimBehind.getViewAlpha());
     }
 
     private boolean shouldFadeAwayWallpaper() {
@@ -491,21 +498,25 @@
      */
     public void setNotificationsBounds(float left, float top, float right, float bottom) {
         mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
+        if (mClipsQsScrim) {
+            mScrimBehind.setBottomEdgePosition((int) top);
+        }
     }
 
     /**
      * Current state of the QuickSettings when pulling it from the top.
      *
      * @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
-     * @param qsPanelBottomY absolute Y position of qs panel bottom
+     * @param qsPanelBottomY Absolute Y position of qs panel bottom
      */
     public void setQsPosition(float expansionFraction, int qsPanelBottomY) {
         if (isNaN(expansionFraction)) {
             return;
         }
-        updateNotificationsScrimAlpha(expansionFraction, qsPanelBottomY);
-        if (mQsExpansion != expansionFraction) {
+        boolean qsBottomVisible = qsPanelBottomY > 0;
+        if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
             mQsExpansion = expansionFraction;
+            mQsBottomVisible = qsBottomVisible;
             boolean relevantState = (mState == ScrimState.SHADE_LOCKED
                     || mState == ScrimState.KEYGUARD
                     || mState == ScrimState.PULSING
@@ -517,22 +528,26 @@
         }
     }
 
-    private void updateNotificationsScrimAlpha(float qsExpansion, int qsPanelBottomY) {
-        float newAlpha = 0;
-        if (qsPanelBottomY > 0) {
-            float interpolator = 0;
-            if (mState == ScrimState.UNLOCKED || mState == ScrimState.SHADE_LOCKED) {
-                interpolator = getInterpolatedFraction();
-            } else {
-                interpolator = qsExpansion;
-            }
-            newAlpha = MathUtils.lerp(0, 1, interpolator);
+    /**
+     * If QS and notification scrims should not overlap, and should be clipped to each other's
+     * bounds instead.
+     */
+    public void setClipsQsScrim(boolean clipScrim) {
+        if (clipScrim == mClipsQsScrim) {
+            return;
         }
-        if (newAlpha != mNotificationsAlpha) {
-            mNotificationsAlpha = newAlpha;
-            // update alpha without animating
-            mNotificationsScrim.setViewAlpha(newAlpha);
+        mClipsQsScrim = clipScrim;
+        for (ScrimState state : ScrimState.values()) {
+            state.setClipQsScrim(mClipsQsScrim);
         }
+        if (mScrimBehind != null) {
+            mScrimBehind.enableBottomEdgeConcave(mClipsQsScrim);
+        }
+    }
+
+    @VisibleForTesting
+    public boolean getClipQsScrim() {
+        return mClipsQsScrim;
     }
 
     private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
@@ -541,7 +556,8 @@
         }
 
         float alpha = getCurrentScrimAlpha(scrim);
-        if (isAnimating(scrim)) {
+        boolean qsScrimPullingDown = scrim == mScrimBehind && mQsBottomVisible;
+        if (isAnimating(scrim) && !qsScrimPullingDown) {
             // Adapt current animation.
             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
@@ -562,7 +578,19 @@
             return;
         }
 
-        if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
+        if (mState == ScrimState.UNLOCKED) {
+            // Darken scrim as you pull down the shade when unlocked
+            float behindFraction = getInterpolatedFraction();
+            behindFraction = (float) Math.pow(behindFraction, 0.8f);
+            if (mClipsQsScrim) {
+                mBehindAlpha = 1;
+                mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
+            } else {
+                mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+                mNotificationsAlpha = mBehindAlpha;
+            }
+            mInFrontAlpha = 0;
+        } else if (mState == ScrimState.BUBBLE_EXPANDED) {
             // Darken scrim as you pull down the shade when unlocked
             float behindFraction = getInterpolatedFraction();
             behindFraction = (float) Math.pow(behindFraction, 0.8f);
@@ -573,27 +601,45 @@
             // Either darken of make the scrim transparent when you
             // pull down the shade
             float interpolatedFract = getInterpolatedFraction();
-            float alphaBehind = mState.getBehindAlpha();
+            float stateBehind = mClipsQsScrim ? mState.getNotifAlpha() : mState.getBehindAlpha();
+            float backAlpha;
             if (mDarkenWhileDragging) {
-                mBehindAlpha = MathUtils.lerp(mDefaultScrimAlpha, alphaBehind,
+                backAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
                         interpolatedFract);
-                mInFrontAlpha = mState.getFrontAlpha();
             } else {
-                mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
+                backAlpha = MathUtils.lerp(0 /* start */, stateBehind,
                         interpolatedFract);
-                mInFrontAlpha = mState.getFrontAlpha();
             }
-            mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
-                    mState.getBehindTint(), interpolatedFract);
+            mInFrontAlpha = mState.getFrontAlpha();
+            int backTint;
+            if (mClipsQsScrim) {
+                backTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
+                        mState.getNotifTint(), interpolatedFract);
+            } else {
+                backTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+                        mState.getBehindTint(), interpolatedFract);
+            }
             if (mQsExpansion > 0) {
-                mBehindAlpha = MathUtils.lerp(mBehindAlpha, mDefaultScrimAlpha, mQsExpansion);
-                mBehindTint = ColorUtils.blendARGB(mBehindTint,
-                        ScrimState.SHADE_LOCKED.getBehindTint(), mQsExpansion);
+                backAlpha = MathUtils.lerp(backAlpha, mDefaultScrimAlpha, mQsExpansion);
+                int stateTint = mClipsQsScrim ? ScrimState.SHADE_LOCKED.getNotifTint()
+                        : ScrimState.SHADE_LOCKED.getBehindTint();
+                backTint = ColorUtils.blendARGB(backTint, stateTint, mQsExpansion);
+            }
+            if (mClipsQsScrim) {
+                mNotificationsAlpha = backAlpha;
+                mNotificationsTint = backTint;
+                mBehindAlpha = 1;
+                mBehindTint = Color.BLACK;
+            } else {
+                mBehindAlpha = backAlpha;
+                mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion);
+                mBehindTint = backTint;
             }
         }
-        if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) {
+        if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
             throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
-                    + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha);
+                    + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
+                    + mNotificationsAlpha);
         }
     }
 
@@ -606,7 +652,7 @@
         setOrAdaptCurrentAnimation(mNotificationsScrim);
         setOrAdaptCurrentAnimation(mScrimInFront);
         setOrAdaptCurrentAnimation(mScrimForBubble);
-        dispatchScrimState(mScrimBehind.getViewAlpha());
+        dispatchBackScrimState(mScrimBehind.getViewAlpha());
 
         // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
         // and docking.
@@ -693,10 +739,10 @@
                     && !mBlankScreen;
 
             mScrimInFront.setColors(mColors, animateScrimInFront);
-            mScrimBehind.setColors(mColors, animateScrimNotifications);
+            mScrimBehind.setColors(mColors, animateBehindScrim);
             mNotificationsScrim.setColors(mColors, animateScrimNotifications);
 
-            dispatchScrimState(mScrimBehind.getViewAlpha());
+            dispatchBackScrimState(mScrimBehind.getViewAlpha());
         }
 
         // We want to override the back scrim opacity for the AOD state
@@ -723,15 +769,21 @@
         dispatchScrimsVisible();
     }
 
-    private void dispatchScrimState(float alpha) {
+    private void dispatchBackScrimState(float alpha) {
+        // When clipping QS, the notification scrim is the one that feels behind.
+        // mScrimBehind will be drawing black and its opacity will always be 1.
+        if (mClipsQsScrim && mQsBottomVisible) {
+            alpha = mNotificationsAlpha;
+        }
         mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
     }
 
     private void dispatchScrimsVisible() {
+        final ScrimView backScrim = mClipsQsScrim ? mNotificationsScrim : mScrimBehind;
         final int currentScrimVisibility;
-        if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
+        if (mScrimInFront.getViewAlpha() == 1 || backScrim.getViewAlpha() == 1) {
             currentScrimVisibility = OPAQUE;
-        } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
+        } else if (mScrimInFront.getViewAlpha() == 0 && backScrim.getViewAlpha() == 0) {
             currentScrimVisibility = TRANSPARENT;
         } else {
             currentScrimVisibility = SEMI_TRANSPARENT;
@@ -859,7 +911,7 @@
         } else if (scrim == mScrimBehind) {
             return mBehindTint;
         } else if (scrim == mNotificationsScrim) {
-            return Color.TRANSPARENT;
+            return mNotificationsTint;
         } else if (scrim == mScrimForBubble) {
             return mBubbleTint;
         } else {
@@ -917,9 +969,11 @@
         if (mState == ScrimState.UNLOCKED) {
             mInFrontTint = Color.TRANSPARENT;
             mBehindTint = mState.getBehindTint();
+            mNotificationsTint = mState.getNotifTint();
             mBubbleTint = Color.TRANSPARENT;
             updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
             updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
+            updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint);
             if (mScrimForBubble != null) {
                 updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
             }
@@ -964,7 +1018,7 @@
         }
 
         if (scrim == mScrimBehind) {
-            dispatchScrimState(alpha);
+            dispatchBackScrimState(alpha);
         }
 
         final boolean wantsAlphaUpdate = alpha != currentAlpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index a9774d8..66cc26f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -22,7 +22,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
@@ -80,11 +80,16 @@
             }
             mFrontTint = Color.BLACK;
             mBehindTint = Color.BLACK;
+            mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
             mBubbleTint = Color.TRANSPARENT;
 
             mFrontAlpha = 0;
-            mBehindAlpha = mScrimBehindAlphaKeyguard;
+            mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard;
+            mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0;
             mBubbleAlpha = 0;
+            if (mClipQsScrim) {
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+            }
         }
     },
 
@@ -105,7 +110,10 @@
     BOUNCER {
         @Override
         public void prepare(ScrimState previousState) {
-            mBehindAlpha = mDefaultScrimAlpha;
+            mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
+            mBehindTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+            mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
+            mNotifTint = Color.TRANSPARENT;
             mFrontAlpha = 0f;
             mBubbleAlpha = 0f;
         }
@@ -126,10 +134,15 @@
     SHADE_LOCKED {
         @Override
         public void prepare(ScrimState previousState) {
-            mBehindAlpha = mDefaultScrimAlpha;
+            mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
+            mNotifAlpha = 1f;
             mBubbleAlpha = 0f;
             mFrontAlpha = 0f;
             mBehindTint = Color.BLACK;
+
+            if (mClipQsScrim) {
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+            }
         }
 
         // to make sure correct color is returned before "prepare" is called
@@ -224,7 +237,8 @@
         @Override
         public void prepare(ScrimState previousState) {
             // State that UI will sync to.
-            mBehindAlpha = 0;
+            mBehindAlpha = mClipQsScrim ? 1 : 0;
+            mNotifAlpha = 0;
             mFrontAlpha = 0;
             mBubbleAlpha = 0;
 
@@ -253,6 +267,10 @@
                 mBubbleTint = Color.BLACK;
                 mBlankScreen = true;
             }
+
+            if (mClipQsScrim) {
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+            }
         }
     },
 
@@ -279,12 +297,14 @@
     int mFrontTint = Color.TRANSPARENT;
     int mBehindTint = Color.TRANSPARENT;
     int mBubbleTint = Color.TRANSPARENT;
+    int mNotifTint = Color.TRANSPARENT;
 
     boolean mAnimateChange = true;
     float mAodFrontScrimAlpha;
     float mFrontAlpha;
     float mBehindAlpha;
     float mBubbleAlpha;
+    float mNotifAlpha;
 
     float mScrimBehindAlphaKeyguard;
     float mDefaultScrimAlpha;
@@ -301,6 +321,7 @@
     boolean mWakeLockScreenSensorActive;
     boolean mKeyguardFadingAway;
     long mKeyguardFadingAwayDuration;
+    boolean mClipQsScrim;
 
     public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
             DozeParameters dozeParameters, DockManager dockManager) {
@@ -325,6 +346,10 @@
         return mBehindAlpha;
     }
 
+    public float getNotifAlpha() {
+        return mNotifAlpha;
+    }
+
     public float getBubbleAlpha() {
         return mBubbleAlpha;
     }
@@ -337,6 +362,10 @@
         return mBehindTint;
     }
 
+    public int getNotifTint() {
+        return mNotifTint;
+    }
+
     public int getBubbleTint() {
         return mBubbleTint;
     }
@@ -406,4 +435,8 @@
         mKeyguardFadingAway = fadingAway;
         mKeyguardFadingAwayDuration = duration;
     }
+
+    public void setClipQsScrim(boolean clipsQsScrim) {
+        mClipQsScrim = clipsQsScrim;
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 20b7a12..9d0285a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -21,7 +21,6 @@
 import static android.app.StatusBarManager.WindowType;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.containsType;
@@ -180,6 +179,7 @@
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -202,7 +202,6 @@
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PowerButtonReveal;
 import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 088f947..2b5caf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -364,7 +364,6 @@
                 MetricsEvent.ACTION_LS_NOTE,
                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
         mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH);
-        mNotificationPanel.showTransientIndication(R.string.notification_tap_again);
         ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
         if (previousView != null) {
             previousView.makeInactive(true /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index b823534..b955455 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -63,11 +63,11 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 141b9f7..39a8bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -32,8 +32,9 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
+import com.android.wm.shell.startingsurface.tv.TvStartingWindowTypeAlgorithm;
 import com.android.wm.shell.transition.Transitions;
 
 import dagger.Module;
@@ -80,4 +81,14 @@
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
+
+    //
+    // Starting Windows (Splash Screen)
+    //
+
+    @WMSingleton
+    @Provides
+    static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() {
+        return new TvStartingWindowTypeAlgorithm();
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index f96d344..4c9ba66 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -72,6 +72,7 @@
 import com.android.wm.shell.splitscreen.SplitScreenController;
 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.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -381,8 +382,10 @@
     @WMSingleton
     @Provides
     static StartingWindowController provideStartingWindowController(Context context,
-            @ShellSplashscreenThread ShellExecutor splashScreenExecutor, TransactionPool pool) {
-        return new StartingWindowController(context, splashScreenExecutor, pool);
+            @ShellSplashscreenThread ShellExecutor splashScreenExecutor,
+            StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, TransactionPool pool) {
+        return new StartingWindowController(context, splashScreenExecutor,
+                startingWindowTypeAlgorithm, pool);
     }
 
     //
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 743dd46..36fd9be 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -54,6 +54,8 @@
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
+import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
@@ -229,4 +231,14 @@
                 menuController, pipSnapAlgorithm, pipTransitionController,
                 floatingContentCoordinator);
     }
+
+    //
+    // Starting Windows (Splash Screen)
+    //
+
+    @WMSingleton
+    @Provides
+    static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() {
+        return new PhoneStartingWindowTypeAlgorithm();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index fe2103c..724f8a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -42,6 +42,7 @@
         when(params.getSelectivelyRegisterSensorsUsingProx()).thenReturn(false);
         when(params.singleTapUsesProx()).thenReturn(true);
         when(params.longPressUsesProx()).thenReturn(true);
+        when(params.getQuickPickupAodDuration()).thenReturn(500);
 
         doneHolder[0] = true;
         return params;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 8eb0e9c..4a9d66c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -35,10 +35,12 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
@@ -76,6 +78,8 @@
     private ProximitySensor.ProximityCheck mProximityCheck;
     @Mock
     private AuthController mAuthController;
+    @Mock
+    private UiEventLogger mUiEventLogger;
 
     private DozeTriggers mTriggers;
     private FakeSensorManager mSensors;
@@ -107,7 +111,7 @@
         mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
                 asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
                 mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
-                mAuthController, mExecutor);
+                mAuthController, mExecutor, mUiEventLogger);
         mTriggers.setDozeMachine(mMachine);
         waitForSensorManager();
     }
@@ -195,6 +199,40 @@
     }
 
     @Test
+    public void testQuickPickup() {
+        // GIVEN device is in doze (screen blank, but running doze sensors)
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+        // WHEN quick pick up is triggered
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null);
+
+        // THEN device goes into aod (shows clock with black background)
+        verify(mMachine).requestState(DozeMachine.State.DOZE_AOD);
+
+        // THEN a log is taken that quick pick up was triggered
+        verify(mUiEventLogger).log(DozingUpdateUiEvent.DOZING_UPDATE_QUICK_PICKUP);
+    }
+
+    @Test
+    public void testQuickPickupTimeOutAfterExecutables() {
+        // GIVEN quick pickup is triggered when device is in DOZE
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null);
+        verify(mMachine).requestState(DozeMachine.State.DOZE_AOD);
+        verify(mMachine, never()).requestState(DozeMachine.State.DOZE);
+
+        // WHEN next executable is run
+        mExecutor.advanceClockToLast();
+        mExecutor.runAllReady();
+
+        // THEN device goes back into DOZE
+        verify(mMachine).requestState(DozeMachine.State.DOZE);
+
+        // THEN a log is taken that wake up timeout expired
+        verify(mUiEventLogger).log(DozingUpdateUiEvent.DOZING_UPDATE_WAKE_TIMEOUT);
+    }
+
+    @Test
     public void testOnSensor_Fingerprint() {
         final int screenX = 100;
         final int screenY = 100;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 609b847..4a487be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -74,7 +74,7 @@
 
         mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
                 new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, true,
-                false, KEY, false, false, false);
+                false, KEY, false, false, false, 0L);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 36b6527..a9d256b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media
 
+import android.app.smartspace.SmartspaceTarget
 import android.graphics.Color
 import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
@@ -47,6 +48,7 @@
 private const val ARTIST = "ARTIST"
 private const val TITLE = "TITLE"
 private const val DEVICE_NAME = "DEVICE_NAME"
+private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
 
 private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
 private fun <T> any(): T = Mockito.any()
@@ -68,6 +70,8 @@
     private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
     @Mock
     private lateinit var executor: Executor
+    @Mock
+    private lateinit var smartspaceData: SmartspaceTarget
 
     private lateinit var mediaDataFilter: MediaDataFilter
     private lateinit var dataMain: MediaData
@@ -91,6 +95,8 @@
 
         dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
             emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
+
+        `when`(smartspaceData.smartspaceTargetId).thenReturn(SMARTSPACE_KEY)
     }
 
     private fun setUser(id: Int) {
@@ -212,6 +218,61 @@
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
         mediaDataFilter.onSwipeToDismiss()
 
-        verify(mediaDataManager).setTimedOut(eq(KEY), eq(true))
+        verify(mediaDataManager).setTimedOut(eq(KEY), eq(true), eq(true))
+    }
+
+    @Test
+    fun testOnSmartspaceMediaDataLoaded_noMedia_usesSmartspace() {
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData))
+        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+    }
+
+    @Test
+    fun testOnSmartspaceMediaDataLoaded_noRecentMedia_usesSmartspace() {
+        val dataOld = dataMain.copy(active = false, lastActive = 0L)
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData))
+        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+    }
+
+    @Test
+    fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_usesMedia() {
+        // WHEN we have media that was recently played, but not currently active
+        val dataCurrent = dataMain.copy(active = false, lastActive = System.currentTimeMillis())
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent))
+
+        // AND we get a smartspace signal
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        // THEN we should tell listeners to treat the media as active instead
+        val dataCurrentAndActive = dataCurrent.copy(active = true)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive))
+        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+    }
+
+    @Test
+    fun testOnSmartspaceMediaDataRemoved_usedSmartspace_clearsMedia() {
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+        mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+
+        verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+    }
+
+    @Test
+    fun testOnSmartspaceMediaDataRemoved_usedMedia_clearsMedia() {
+        val dataCurrent = dataMain.copy(active = false, lastActive = System.currentTimeMillis())
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrent))
+        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 96eb4b0..678f89a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -60,6 +60,7 @@
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
+    @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
     lateinit var session: MediaSession
     lateinit var metadataBuilder: MediaMetadata.Builder
     lateinit var backgroundExecutor: FakeExecutor
@@ -118,6 +119,9 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+        whenever(controller.playbackInfo).thenReturn(playbackInfo)
+        whenever(playbackInfo.playbackType).thenReturn(
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
 
         // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
         // listeners in the internal processing pipeline. It receives events, but ince it is a
@@ -230,6 +234,27 @@
     }
 
     @Test
+    fun testOnNotificationRemoved_withResumption_butNotLocal() {
+        // GIVEN that the manager has a notification with a resume action, but is not local
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        whenever(playbackInfo.playbackType).thenReturn(
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
+        val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, isLocalSession = false)
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
+
+        // WHEN the notification is removed
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the media data is removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
     fun testAppBlockedFromResumption() {
         // GIVEN that the manager has a notification with a resume action
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -280,11 +305,12 @@
 
     @Test
     fun testAddResumptionControls() {
-        // WHEN resumption controls are added`
+        // WHEN resumption controls are added
         val desc = MediaDescription.Builder().run {
             setTitle(SESSION_TITLE)
             build()
         }
+        val currentTimeMillis = System.currentTimeMillis()
         mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
                 APP_NAME, pendingIntent, PACKAGE_NAME)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
@@ -296,6 +322,7 @@
         assertThat(data.song).isEqualTo(SESSION_TITLE)
         assertThat(data.app).isEqualTo(APP_NAME)
         assertThat(data.actions).hasSize(1)
+        assertThat(data.lastActive).isAtLeast(currentTimeMillis)
     }
 
     @Test
@@ -350,4 +377,52 @@
         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
         verify(listener).onSmartspaceMediaDataRemoved(KEY_MEDIA_SMARTSPACE)
     }
+
+    @Test
+    fun testOnMediaDataChanged_updatesLastActiveTime() {
+        val currentTimeMillis = System.currentTimeMillis()
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTimeMillis)
+    }
+
+    @Test
+    fun testOnMediaDataTimedOut_doesNotUpdateLastActiveTime() {
+        // GIVEN that the manager has a notification
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+        // WHEN the notification times out
+        val currentTimeMillis = System.currentTimeMillis()
+        mediaDataManager.setTimedOut(KEY, true, true)
+
+        // THEN the last active time is not changed
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTimeMillis)
+    }
+
+    @Test
+    fun testOnActiveMediaConverted_doesNotUpdateLastActiveTime() {
+        // GIVEN that the manager has a notification with a resume action
+        whenever(controller.metadata).thenReturn(metadataBuilder.build())
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isFalse()
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+
+        // WHEN the notification is removed
+        val currentTimeMillis = System.currentTimeMillis()
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // THEN the last active time is not changed
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTimeMillis)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 59c2b17..96d1d94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -182,6 +182,16 @@
     }
 
     @Test
+    fun testOnLoad_remotePlayback_doesNotCheck() {
+        // When media data is loaded that has not been checked yet, and is not local
+        val dataRemote = data.copy(isLocalSession = false)
+        resumeListener.onMediaDataLoaded(KEY, null, dataRemote)
+
+        // Then we do not take action
+        verify(mediaDataManager, never()).setResumeAction(any(), any())
+    }
+
+    @Test
     fun testOnLoad_checksForResume_hasService() {
         // Set up mocks to successfully find a MBS that returns valid media
         val pm = mock(PackageManager::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java
index 7cddc3f..1b713dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java
@@ -19,6 +19,7 @@
 
 import static com.android.systemui.people.NotificationHelper.getHighestPriorityNotification;
 import static com.android.systemui.people.NotificationHelper.getMessagingStyleMessages;
+import static com.android.systemui.people.NotificationHelper.getSenderIfGroupConversation;
 import static com.android.systemui.people.NotificationHelper.isMissedCall;
 import static com.android.systemui.people.NotificationHelper.isMissedCallOrHasContent;
 import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
@@ -32,6 +33,7 @@
 import android.app.Person;
 import android.content.pm.ShortcutInfo;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
@@ -202,4 +204,53 @@
         assertThat(getHighestPriorityNotification(notifications))
                 .isEqualTo(mNotificationEntry1);
     }
+
+    @Test
+    public void testGetSenderIfGroupConversation_notGroup() {
+        Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message(
+                NOTIFICATION_TEXT_3, 10, PERSON);
+        Notification notification = new Notification.Builder(mContext, "test")
+                .setContentTitle("TEST_TITLE")
+                .setContentText("TEST_TEXT")
+                .setShortcutId(SHORTCUT_ID_1)
+                .setStyle(new Notification.MessagingStyle(PERSON).addMessage(message))
+                .build();
+        assertThat(getSenderIfGroupConversation(notification, message)).isNull();
+    }
+
+    @Test
+    public void testGetSenderIfGroupConversation_group() {
+        Bundle extras = new Bundle();
+        extras.putBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION, true);
+        Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message(
+                NOTIFICATION_TEXT_3, 10, PERSON);
+
+        Notification notification = new Notification.Builder(mContext, "test")
+                .setContentTitle("TEST_TITLE")
+                .setContentText("TEST_TEXT")
+                .setShortcutId(SHORTCUT_ID_1)
+                .setStyle(new Notification.MessagingStyle(PERSON)
+                        .setGroupConversation(true)
+                        .addMessage(message))
+                .addExtras(extras)
+                .build();
+        assertThat(getSenderIfGroupConversation(notification, message)).isEqualTo("name");
+    }
+
+    @Test
+    public void testGetSenderIfGroupConversation_groupNoName() {
+        Bundle extras = new Bundle();
+        extras.putBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION, true);
+        Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message(
+                NOTIFICATION_TEXT_3, 10, new Person.Builder().build());
+
+        Notification notification = new Notification.Builder(mContext, "test")
+                .setContentTitle("TEST_TITLE")
+                .setContentText("TEST_TEXT")
+                .setShortcutId(SHORTCUT_ID_1)
+                .setStyle(new Notification.MessagingStyle(PERSON).addMessage(message))
+                .setExtras(extras)
+                .build();
+        assertThat(getSenderIfGroupConversation(notification, message)).isNull();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index c929073..cc322620 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -59,6 +59,7 @@
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleTileKey;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -94,6 +95,7 @@
     private static final Uri URI = Uri.parse("fake_uri");
     private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
     private static final String NAME = "username";
+    private static final UserHandle USER = new UserHandle(0);
     private static final Person PERSON = new Person.Builder()
             .setName("name")
             .setKey("abc")
@@ -103,6 +105,7 @@
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
                     .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setUserHandle(USER)
                     .setLastInteractionTimestamp(123L)
                     .setNotificationKey(NOTIFICATION_KEY)
                     .setNotificationContent(NOTIFICATION_CONTENT)
@@ -231,10 +234,51 @@
                         .setPackageName(PACKAGE_NAME)
                         .setUserHandle(new UserHandle(0))
                         .build();
+        PeopleTileKey key = new PeopleTileKey(tile);
         PeopleSpaceTile actual = PeopleSpaceUtils
-                .augmentTileFromNotification(mContext, tile, mNotificationEntry1, 0);
+                .augmentTileFromNotification(mContext, tile, key, mNotificationEntry1, 0);
 
         assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+        assertThat(actual.getNotificationSender()).isEqualTo(null);
+    }
+
+    @Test
+    public void testAugmentTileFromNotificationGroupWithSender() {
+        Bundle extras = new Bundle();
+        extras.putBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION, true);
+        Notification notification = new Notification.Builder(mContext, "test")
+                .setContentTitle("TEST_TITLE")
+                .setContentText("TEST_TEXT")
+                .setShortcutId(SHORTCUT_ID_1)
+                .setStyle(new Notification.MessagingStyle(PERSON)
+                        .setGroupConversation(true)
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                NOTIFICATION_TEXT_1, 0, PERSON))
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                NOTIFICATION_TEXT_2, 20, PERSON))
+                        .addMessage(new Notification.MessagingStyle.Message(
+                                NOTIFICATION_TEXT_3, 10, PERSON))
+                )
+                .setExtras(extras)
+                .build();
+        NotificationEntry notificationEntry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_1).build())
+                .setUser(UserHandle.of(0))
+                .setPkg(PACKAGE_NAME)
+                .build();
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUserHandle(new UserHandle(0))
+                        .build();
+        PeopleTileKey key = new PeopleTileKey(tile);
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromNotification(mContext, tile, key, notificationEntry, 0);
+
+        assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+        assertThat(actual.getNotificationSender().toString()).isEqualTo("name");
     }
 
     @Test
@@ -245,8 +289,9 @@
                         .setPackageName(PACKAGE_NAME)
                         .setUserHandle(new UserHandle(0))
                         .build();
+        PeopleTileKey key = new PeopleTileKey(tile);
         PeopleSpaceTile actual = PeopleSpaceUtils
-                .augmentTileFromNotification(mContext, tile, mNotificationEntry3, 0);
+                .augmentTileFromNotification(mContext, tile, key, mNotificationEntry3, 0);
 
         assertThat(actual.getNotificationContent()).isEqualTo(null);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index 3cc55f2..d353d52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -42,6 +42,7 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.util.DisplayMetrics;
 import android.view.View;
@@ -73,10 +74,13 @@
     private static final String GAME_DESCRIPTION = "Playing a game!";
     private static final CharSequence MISSED_CALL = "Custom missed call message";
     private static final String NAME = "username";
+    private static final UserHandle USER = new UserHandle(0);
+    private static final String SENDER = "sender";
     private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
             new PeopleSpaceTile
                     .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
                     .setLastInteractionTimestamp(0L)
+                    .setUserHandle(USER)
                     .build();
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
@@ -85,6 +89,16 @@
                     .setNotificationKey(NOTIFICATION_KEY)
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
+                    .setUserHandle(USER)
+                    .build();
+    private static final PeopleSpaceTile PERSON_TILE_WITH_SENDER =
+            new PeopleSpaceTile
+                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setLastInteractionTimestamp(123L)
+                    .setNotificationKey(NOTIFICATION_KEY)
+                    .setNotificationContent(NOTIFICATION_CONTENT)
+                    .setNotificationSender(SENDER)
+                    .setUserHandle(USER)
                     .build();
     private static final ConversationStatus GAME_STATUS =
             new ConversationStatus
@@ -534,6 +548,86 @@
     }
 
     @Test
+    public void testCreateRemoteViewsWithNotificationWithSenderTemplate() {
+        PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE_WITH_SENDER.toBuilder()
+                .setNotificationDataUri(null)
+                .setStatuses(Arrays.asList(GAME_STATUS,
+                        NEW_STORY_WITH_AVAILABILITY)).build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithStatusAndNotification, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        TextView subtext = (TextView) result.findViewById(R.id.subtext);
+        assertEquals(View.VISIBLE, result.findViewById(R.id.subtext).getVisibility());
+        assertEquals(subtext.getText(), SENDER);
+        assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // Has notification content.
+        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+
+        // Subtract one from lines because sender is included.
+        assertThat(statusContent.getMaxLines()).isEqualTo(2);
+
+        // Has a single message, no count shown.
+        assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_medium) - 1);
+        RemoteViews smallView = new PeopleTileViewHelper(mContext,
+                tileWithStatusAndNotification, 0, mOptions).getViews();
+        View smallResult = smallView.apply(mContext, null);
+
+        // Show icon instead of name.
+        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.person_icon).getVisibility());
+
+        // Has a single message, no count shown.
+        assertEquals(View.GONE, smallResult.findViewById(R.id.messages_count).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                tileWithStatusAndNotification, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        name = (TextView) largeResult.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        subtext = (TextView) largeResult.findViewById(R.id.subtext);
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.subtext).getVisibility());
+        assertEquals(subtext.getText(), SENDER);
+        assertEquals(View.GONE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        View personIcon = largeResult.findViewById(R.id.person_icon);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        // Has notification content.
+        statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+
+        // Subtract one from lines because sender is included.
+        assertThat(statusContent.getMaxLines()).isEqualTo(2);
+
+        // Has a single message, no count shown.
+        assertEquals(View.GONE, largeResult.findViewById(R.id.messages_count).getVisibility());
+
+    }
+
+    @Test
     public void testCreateRemoteViewsWithNotificationTemplateTwoMessages() {
         PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
                 .setNotificationDataUri(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 725e5d4..411fb02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -1201,7 +1201,8 @@
                         .setPackageName(TEST_PACKAGE_A)
                         .setUserHandle(new UserHandle(0))
                         .build();
-        PeopleSpaceTile actual = mManager.augmentTileFromNotifications(tile, EMPTY_STRING,
+        PeopleTileKey key = new PeopleTileKey(tile);
+        PeopleSpaceTile actual = mManager.augmentTileFromNotifications(tile, key, EMPTY_STRING,
                         Map.of(new PeopleTileKey(mNotificationEntry),
                                 new HashSet<>(Collections.singleton(mNotificationEntry))));
 
@@ -1216,8 +1217,9 @@
                         .setPackageName(TEST_PACKAGE_A)
                         .setUserHandle(new UserHandle(0))
                         .build();
+        PeopleTileKey key = new PeopleTileKey(tile);
         PeopleSpaceTile actual = mManager
-                .augmentTileFromNotifications(tile, EMPTY_STRING,
+                .augmentTileFromNotifications(tile, key, EMPTY_STRING,
                         Map.of(new PeopleTileKey(mNotificationEntry),
                                 new HashSet<>(Collections.singleton(mNotificationEntry))));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java
index c2e58ef..a345f78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/scrim/ScrimViewTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.scrim;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -31,7 +31,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.drawable.ScrimDrawable;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index f1c8ece..a63d509 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -123,6 +123,7 @@
         verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
         verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
                 anyFloat());
+        verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
     }
@@ -170,6 +171,7 @@
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
         verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
                 anyFloat());
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
     }
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 8633eb4..559ee6f 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
@@ -49,8 +49,8 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -69,6 +69,7 @@
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 
@@ -91,7 +92,7 @@
     @Mock
     private AlarmManager mAlarmManager;
     @Mock
-    private DozeParameters mDozeParamenters;
+    private DozeParameters mDozeParameters;
     @Mock
     LightBarController mLightBarController;
     @Mock
@@ -200,8 +201,8 @@
             return null;
         }).when(mScrimBehind).postOnAnimationDelayed(any(Runnable.class), anyLong());
 
-        when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
-        when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
+        when(mDozeParameters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
+        when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
 
         doAnswer((Answer<Void>) invocation -> {
             mScrimState = invocation.getArgument(0);
@@ -220,7 +221,7 @@
         when(mDockManager.isDocked()).thenReturn(false);
 
         mScrimController = new ScrimController(mLightBarController,
-                mDozeParamenters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
+                mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
                 new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
                 mDockManager, mConfigurationController, mFeatureFlags,
                 new FakeExecutor(new FakeSystemClock()));
@@ -238,6 +239,10 @@
     @After
     public void tearDown() {
         finishAnimationsImmediately();
+        Arrays.stream(ScrimState.values()).forEach((scrim) -> {
+            scrim.setAodFrontScrimAlpha(0f);
+            scrim.setClipQsScrim(false);
+        });
         DejankUtils.setImmediate(false);
     }
 
@@ -259,9 +264,30 @@
     @Test
     public void transitionToShadeLocked() {
         mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
+        mScrimController.setQsPosition(1f, 0);
         finishAnimationsImmediately();
 
         assertScrimAlpha(Map.of(
+                mNotificationsScrim, OPAQUE,
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
+        assertScrimTinted(Map.of(
+                mScrimInFront, false,
+                mScrimBehind, true,
+                mScrimForBubble, false
+        ));
+    }
+
+    @Test
+    public void transitionToShadeLocked_clippingQs() {
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
+        mScrimController.setQsPosition(1f, 0);
+        finishAnimationsImmediately();
+
+        assertScrimAlpha(Map.of(
+                mNotificationsScrim, OPAQUE,
                 mScrimInFront, TRANSPARENT,
                 mScrimBehind, OPAQUE));
 
@@ -403,9 +429,6 @@
         mScrimController.setAodFrontScrimAlpha(0.3f);
         Assert.assertEquals(ScrimState.AOD.getFrontAlpha(), mScrimInFront.getViewAlpha(), 0.001f);
         Assert.assertNotEquals(0.3f, mScrimInFront.getViewAlpha(), 0.001f);
-
-        // Reset value since enums are static.
-        mScrimController.setAodFrontScrimAlpha(0f);
     }
 
     @Test
@@ -500,11 +523,34 @@
         // Back scrim should be visible without tint
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
                 mScrimBehind, OPAQUE));
 
         assertScrimTinted(Map.of(
                 mScrimInFront, false,
                 mScrimBehind, false,
+                mNotificationsScrim, false,
+                mScrimForBubble, false
+        ));
+    }
+
+    @Test
+    public void transitionToKeyguardBouncer_clippingQs() {
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.transitionTo(ScrimState.BOUNCER);
+        finishAnimationsImmediately();
+        // Front scrim should be transparent
+        // Back scrim should be clipping QS
+        // Notif scrim should be visible without tint
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, OPAQUE,
+                mScrimBehind, OPAQUE));
+
+        assertScrimTinted(Map.of(
+                mScrimInFront, false,
+                mScrimBehind, true,
+                mNotificationsScrim, false,
                 mScrimForBubble, false
         ));
     }
@@ -530,9 +576,11 @@
         finishAnimationsImmediately();
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
                 mScrimBehind, TRANSPARENT));
 
         assertScrimTinted(Map.of(
+                mNotificationsScrim, false,
                 mScrimInFront, false,
                 mScrimBehind, true,
                 mScrimForBubble, false
@@ -542,6 +590,7 @@
         mScrimController.setPanelExpansion(0.5f);
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, SEMI_TRANSPARENT,
                 mScrimBehind, SEMI_TRANSPARENT));
     }
 
@@ -617,6 +666,32 @@
     }
 
     @Test
+    public void qsExpansion_clippingQs() {
+        reset(mScrimBehind);
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.setQsPosition(1f, 999 /* value doesn't matter */);
+        finishAnimationsImmediately();
+
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, OPAQUE,
+                mNotificationsScrim, OPAQUE));
+    }
+
+    @Test
+    public void qsExpansion_half_clippingQs() {
+        reset(mScrimBehind);
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.setQsPosition(0.5f, 999 /* value doesn't matter */);
+        finishAnimationsImmediately();
+
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, OPAQUE,
+                mNotificationsScrim, SEMI_TRANSPARENT));
+    }
+
+    @Test
     public void panelExpansionAffectsAlpha() {
         mScrimController.setPanelExpansion(0f);
         mScrimController.setPanelExpansion(0.5f);
@@ -919,13 +994,13 @@
 
     @Test
     public void testAnimatesTransitionToAod() {
-        when(mDozeParamenters.shouldControlScreenOff()).thenReturn(false);
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
         ScrimState.AOD.prepare(ScrimState.KEYGUARD);
         Assert.assertFalse("No animation when ColorFade kicks in",
                 ScrimState.AOD.getAnimateChange());
 
-        reset(mDozeParamenters);
-        when(mDozeParamenters.shouldControlScreenOff()).thenReturn(true);
+        reset(mDozeParameters);
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
         ScrimState.AOD.prepare(ScrimState.KEYGUARD);
         Assert.assertTrue("Animate scrims when ColorFade won't be triggered",
                 ScrimState.AOD.getAnimateChange());
@@ -977,6 +1052,7 @@
         mScrimController.setPanelExpansion(0.5f);
         // notifications scrim alpha change require calling setQsPosition
         mScrimController.setQsPosition(0, 300);
+        finishAnimationsImmediately();
 
         assertScrimAlpha(Map.of(
                 mScrimBehind, SEMI_TRANSPARENT,
@@ -985,6 +1061,21 @@
     }
 
     @Test
+    public void testScrimsVisible_whenShadeVisible_clippingQs() {
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        mScrimController.setPanelExpansion(0.5f);
+        // notifications scrim alpha change require calling setQsPosition
+        mScrimController.setQsPosition(0.5f, 300);
+        finishAnimationsImmediately();
+
+        assertScrimAlpha(Map.of(
+                mScrimBehind, OPAQUE,
+                mNotificationsScrim, SEMI_TRANSPARENT,
+                mScrimInFront, TRANSPARENT));
+    }
+
+    @Test
     public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.setQsPosition(0.5f, 300);
@@ -1008,8 +1099,6 @@
     }
 
     private void assertScrimTinted(Map<ScrimView, Boolean> scrimToTint) {
-        // notifications scrim should have always transparent tint
-        assertScrimTint(mNotificationsScrim, false);
         scrimToTint.forEach((scrim, hasTint) -> assertScrimTint(scrim, hasTint));
     }
 
@@ -1047,6 +1136,12 @@
         }
         scrimToAlpha.forEach((scrimView, alpha) -> assertScrimAlpha(scrimView, alpha));
 
+        // When clipping, QS scrim should not affect combined visibility.
+        if (mScrimController.getClipQsScrim() && scrimToAlpha.get(mScrimBehind) == OPAQUE) {
+            scrimToAlpha = new HashMap<>(scrimToAlpha);
+            scrimToAlpha.remove(mScrimBehind);
+        }
+
         // Check combined scrim visibility.
         final int visibility;
         if (scrimToAlpha.values().contains(OPAQUE)) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 57e5bb2..f1dcdff 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -450,7 +450,7 @@
                         return;
                     }
                     mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                    maybeRequestFillLocked();
+                    maybeRequestFillFromServiceLocked();
                     viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
                 }
             } : null;
@@ -462,7 +462,7 @@
             mPendingInlineSuggestionsRequest = inlineRequest;
         }
 
-        void maybeRequestFillLocked() {
+        void maybeRequestFillFromServiceLocked() {
             if (mPendingFillRequest == null) {
                 return;
             }
@@ -472,9 +472,12 @@
                     return;
                 }
 
-                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
-                        mPendingFillRequest.getFillContexts(), mPendingFillRequest.getClientState(),
-                        mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
+                    mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+                            mPendingFillRequest.getFillContexts(),
+                            mPendingFillRequest.getClientState(),
+                            mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+                }
             }
 
             mRemoteFillService.onFillRequest(mPendingFillRequest);
@@ -581,7 +584,7 @@
                         /*inlineSuggestionsRequest=*/null);
 
                 mPendingFillRequest = request;
-                maybeRequestFillLocked();
+                maybeRequestFillFromServiceLocked();
             }
 
             if (mActivityToken != null) {
@@ -3188,6 +3191,17 @@
             return false;
         }
 
+        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
+        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
+                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
+            if (sDebug) {
+                Slog.d(TAG, "Inline suggestions not supported for "
+                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
+                        + ". Falling back to dropdown.");
+            }
+            return false;
+        }
+
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService == null) {
@@ -3196,7 +3210,7 @@
         }
 
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
-                new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
+                new InlineFillUi.InlineFillUiInfo(request, focusedId,
                         filterText, remoteRenderService, userId, id);
         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                 new InlineFillUi.InlineSuggestionUiCallback() {
@@ -3809,6 +3823,10 @@
             mContexts = new ArrayList<>(1);
         }
 
+        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
+            inlineSuggestionsRequest = null;
+        }
+
         mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
     }
 
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index e47d732..c5246c7 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static android.content.PermissionChecker.PERMISSION_HARD_DENIED;
+import static android.content.PermissionChecker.PID_UNKNOWN;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -45,6 +46,7 @@
 import android.bluetooth.IBluetoothProfileServiceConnection;
 import android.bluetooth.IBluetoothStateChangeCallback;
 import android.content.ActivityNotFoundException;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -306,13 +308,13 @@
                 DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS);
     }
 
-    public boolean onFactoryReset() {
+    public boolean onFactoryReset(AttributionSource attributionSource) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
 
         final long token = Binder.clearCallingIdentity();
         try {
-            return onFactoryResetInternal();
+            return onFactoryResetInternal(attributionSource);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -322,7 +324,7 @@
             android.Manifest.permission.BLUETOOTH_CONNECT,
             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
     })
-    private boolean onFactoryResetInternal() {
+    private boolean onFactoryResetInternal(AttributionSource attributionSource) {
         // Wait for stable state if bluetooth is temporary state.
         int state = getState();
         if (state == BluetoothAdapter.STATE_BLE_TURNING_ON
@@ -351,7 +353,7 @@
                 addActiveLog(
                         BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET,
                         mContext.getPackageName(), false);
-                mBluetooth.disable(mContext.getAttributionSource());
+                mBluetooth.disable(attributionSource);
                 return true;
             }
         } catch (RemoteException e) {
@@ -947,7 +949,8 @@
     }
 
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    private boolean checkBluetoothPermissions(String packageName, boolean requireForeground) {
+    private boolean checkBluetoothPermissions(AttributionSource attributionSource, String message,
+            boolean requireForeground) {
         if (isBluetoothDisallowed()) {
             if (DBG) {
                 Slog.d(TAG, "checkBluetoothPermissions: bluetooth disallowed");
@@ -958,22 +961,24 @@
         final int callingUid = Binder.getCallingUid();
         final boolean isCallerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
         if (!isCallerSystem) {
-            checkPackage(callingUid, packageName);
+            checkPackage(callingUid, attributionSource.getPackageName());
 
             if (requireForeground && !checkIfCallerIsForegroundUser()) {
                 Slog.w(TAG, "Not allowed for non-active and non system user");
                 return false;
             }
 
-            if (!checkConnectPermissionForPreflight(mContext)) {
+            if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, message)) {
                 return false;
             }
         }
         return true;
     }
 
-    public boolean enableBle(String packageName, IBinder token) throws RemoteException {
-        if (!checkBluetoothPermissions(packageName, false)) {
+    public boolean enableBle(AttributionSource attributionSource, IBinder token)
+            throws RemoteException {
+        final String packageName = attributionSource.getPackageName();
+        if (!checkBluetoothPermissions(attributionSource, "enableBle", false)) {
             if (DBG) {
                 Slog.d(TAG, "enableBle(): bluetooth disallowed");
             }
@@ -1003,8 +1008,10 @@
     }
 
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
-    public boolean disableBle(String packageName, IBinder token) throws RemoteException {
-        if (!checkBluetoothPermissions(packageName, false)) {
+    public boolean disableBle(AttributionSource attributionSource, IBinder token)
+            throws RemoteException {
+        final String packageName = attributionSource.getPackageName();
+        if (!checkBluetoothPermissions(attributionSource, "disableBle", false)) {
             if (DBG) {
                 Slog.d(TAG, "disableBLE(): bluetooth disallowed");
             }
@@ -1122,8 +1129,9 @@
 
     }
 
-    public boolean enableNoAutoConnect(String packageName) {
-        if (!checkBluetoothPermissions(packageName, false)) {
+    public boolean enableNoAutoConnect(AttributionSource attributionSource) {
+        final String packageName = attributionSource.getPackageName();
+        if (!checkBluetoothPermissions(attributionSource, "enableNoAutoConnect", false)) {
             if (DBG) {
                 Slog.d(TAG, "enableNoAutoConnect(): not enabling - bluetooth disallowed");
             }
@@ -1149,8 +1157,9 @@
         return true;
     }
 
-    public boolean enable(String packageName) throws RemoteException {
-        if (!checkBluetoothPermissions(packageName, true)) {
+    public boolean enable(AttributionSource attributionSource) throws RemoteException {
+        final String packageName = attributionSource.getPackageName();
+        if (!checkBluetoothPermissions(attributionSource, "enable", true)) {
             if (DBG) {
                 Slog.d(TAG, "enable(): not enabling - bluetooth disallowed");
             }
@@ -1183,8 +1192,10 @@
         return true;
     }
 
-    public boolean disable(String packageName, boolean persist) throws RemoteException {
-        if (!checkBluetoothPermissions(packageName, true)) {
+    public boolean disable(AttributionSource attributionSource, boolean persist)
+            throws RemoteException {
+        final String packageName = attributionSource.getPackageName();
+        if (!checkBluetoothPermissions(attributionSource, "disable", true)) {
             if (DBG) {
                 Slog.d(TAG, "disable(): not disabling - bluetooth disallowed");
             }
@@ -1700,8 +1711,8 @@
         }
     }
 
-    public String getAddress() {
-        if (!checkConnectPermissionForPreflight(mContext)) {
+    public String getAddress(AttributionSource attributionSource) {
+        if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getAddress")) {
             return null;
         }
 
@@ -1734,8 +1745,8 @@
         return mAddress;
     }
 
-    public String getName() {
-        if (!checkConnectPermissionForPreflight(mContext)) {
+    public String getName(AttributionSource attributionSource) {
+        if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getName")) {
             return null;
         }
 
@@ -2879,6 +2890,25 @@
         }
     }
 
+    private static boolean checkPermissionForDataDelivery(Context context, String permission,
+            AttributionSource attributionSource, String message) {
+        final int result = PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                context, permission, PID_UNKNOWN,
+                new AttributionSource(context.getAttributionSource(), attributionSource), message);
+        if (result == PERMISSION_GRANTED) {
+            return true;
+        }
+
+        final String msg = "Need " + permission + " permission for " + attributionSource + ": "
+                + message;
+        if (result == PERMISSION_HARD_DENIED) {
+            throw new SecurityException(msg);
+        } else {
+            Log.w(TAG, msg);
+            return false;
+        }
+    }
+
     /**
      * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns
      * false if the result is a soft denial. Throws SecurityException if the result is a hard
@@ -2887,13 +2917,10 @@
      * <p>Should be used in situations where the app op should not be noted.
      */
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    private static boolean checkConnectPermissionForPreflight(Context context) {
-        int permissionCheckResult = PermissionChecker.checkCallingOrSelfPermissionForPreflight(
-                context, BLUETOOTH_CONNECT);
-        if (permissionCheckResult == PERMISSION_HARD_DENIED) {
-            throw new SecurityException("Need BLUETOOTH_CONNECT permission");
-        }
-        return permissionCheckResult == PERMISSION_GRANTED;
+    public static boolean checkConnectPermissionForDataDelivery(
+            Context context, AttributionSource attributionSource, String message) {
+        return checkPermissionForDataDelivery(context, BLUETOOTH_CONNECT,
+                attributionSource, message);
     }
 
     static @NonNull Bundle getTempAllowlistBroadcastOptions() {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ee3530a..edf832f 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2072,7 +2072,8 @@
 
     private void notifyCellLocationForSubscriber(int subId, CellIdentity cellIdentity,
             boolean hasUserSwitched) {
-        log("notifyCellLocationForSubscriber: subId=" + subId + " cellIdentity=" + cellIdentity);
+        log("notifyCellLocationForSubscriber: subId=" + subId + " cellIdentity="
+                + Rlog.pii(DBG || VDBG || DBG_LOC, cellIdentity));
         if (!checkNotifyPermission("notifyCellLocation()")) {
             return;
         }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c7994c3..206f135 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,6 +70,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
@@ -3078,6 +3079,18 @@
                         + ", uid=" + callingUid
                         + " requires " + r.permission);
                 return new ServiceLookupResult(null, r.permission);
+            } else if (Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE.equals(r.permission)
+                    && callingUid != Process.SYSTEM_UID) {
+                // Hotword detection must run in its own sandbox, and we don't even trust
+                // its enclosing application to bind to it - only the system.
+                // TODO(b/185746653) remove this special case and generalize
+                Slog.w(TAG, "Permission Denial: Accessing service " + r.shortInstanceName
+                        + " from pid=" + callingPid
+                        + ", uid=" + callingUid
+                        + " requiring permission " + r.permission
+                        + " can only be bound to from the system.");
+                return new ServiceLookupResult(null, "can only be bound to "
+                        + "by the system.");
             } else if (r.permission != null && callingPackage != null) {
                 final int opCode = AppOpsManager.permissionToOpCode(r.permission);
                 if (opCode != AppOpsManager.OP_NONE && mAm.getAppOpsManager().checkOpNoThrow(
@@ -5920,6 +5933,10 @@
      * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state.
      */
     private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) {
+        if (!ActivityManagerUtils.shouldSamplePackageForAtom(
+                r.packageName, mAm.mConstants.mDefaultFgsAtomSampleRate)) {
+            return;
+        }
         FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                 r.appInfo.uid,
                 r.shortInstanceName,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index c8363dd..bf57452 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -97,6 +97,7 @@
     static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
     static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration";
     static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
+    static final String KEY_FGS_ATOM_SAMPLE_RATE = "fgs_atom_sample_rate";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -137,6 +138,7 @@
     private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000;
     private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
     private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
+    private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 %
 
     // Flag stored in the DeviceConfig API.
     /**
@@ -430,6 +432,13 @@
      */
     volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS;
 
+    /**
+     * Sample rate for the FGS westworld atom.
+     *
+     * If the value is 0.1, 10% of the installed packages would be sampled.
+     */
+    volatile float mDefaultFgsAtomSampleRate = DEFAULT_FGS_ATOM_SAMPLE_RATE;
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -629,6 +638,9 @@
                             case KEY_FGS_START_FOREGROUND_TIMEOUT:
                                 updateFgsStartForegroundTimeout();
                                 break;
+                            case KEY_FGS_ATOM_SAMPLE_RATE:
+                                updateFgsAtomSamplePercent();
+                                break;
                             default:
                                 break;
                         }
@@ -933,6 +945,13 @@
                 DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS);
     }
 
+    private void updateFgsAtomSamplePercent() {
+        mDefaultFgsAtomSampleRate = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_FGS_ATOM_SAMPLE_RATE,
+                DEFAULT_FGS_ATOM_SAMPLE_RATE);
+    }
+
     private void updateImperceptibleKillExemptions() {
         IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
         IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
@@ -1145,6 +1164,8 @@
         pw.println(mFlagFgsStartRestrictionEnabled);
         pw.print("  "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK);
         pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk);
+        pw.print("  "); pw.print(KEY_FGS_ATOM_SAMPLE_RATE);
+        pw.print("="); pw.println(mDefaultFgsAtomSampleRate);
 
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
new file mode 100644
index 0000000..dd24148
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -0,0 +1,124 @@
+/*
+ * 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.am;
+
+import android.app.ActivityThread;
+import android.provider.Settings;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * To store random utility methods...
+ */
+public class ActivityManagerUtils {
+    private ActivityManagerUtils() {
+    }
+
+    private static Integer sAndroidIdHash;
+
+    @GuardedBy("sHashCache")
+    private static final ArrayMap<String, Integer> sHashCache = new ArrayMap<>();
+
+    private static String sInjectedAndroidId;
+
+    /** Used by the unit tests to inject an android ID. Do not set in the prod code. */
+    @VisibleForTesting
+    static void injectAndroidIdForTest(String androidId) {
+        sInjectedAndroidId = androidId;
+        sAndroidIdHash = null;
+    }
+
+    /**
+     * Return a hash between [0, MAX_VALUE] generated from the android ID.
+     */
+    @VisibleForTesting
+    static int getAndroidIdHash() {
+        // No synchronization is required. Double-initialization is fine here.
+        if (sAndroidIdHash == null) {
+            final String androidId = Settings.Secure.getString(
+                    ActivityThread.currentApplication().getContentResolver(),
+                    Settings.Secure.ANDROID_ID);
+            sAndroidIdHash = getUnsignedHashUnCached(
+                    sInjectedAndroidId != null ? sInjectedAndroidId : androidId);
+        }
+        return sAndroidIdHash;
+    }
+
+    /**
+     * Return a hash between [0, MAX_VALUE] generated from a package name, using a cache.
+     *
+     * Because all the results are cached, do not use it for dynamically generated strings.
+     */
+    @VisibleForTesting
+    static int getUnsignedHashCached(String s) {
+        synchronized (sHashCache) {
+            final Integer cached = sHashCache.get(s);
+            if (cached != null) {
+                return cached;
+            }
+            final int hash = getUnsignedHashUnCached(s);
+            sHashCache.put(s.intern(), hash);
+            return hash;
+        }
+    }
+
+    /**
+     * Return a hash between [0, MAX_VALUE] generated from a package name.
+     */
+    private static int getUnsignedHashUnCached(String s) {
+        try {
+            final MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            digest.update(s.getBytes());
+            return unsignedIntFromBytes(digest.digest());
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @VisibleForTesting
+    static int unsignedIntFromBytes(byte[] longEnoughBytes) {
+        return (extractByte(longEnoughBytes, 0)
+                | extractByte(longEnoughBytes, 1)
+                | extractByte(longEnoughBytes, 2)
+                | extractByte(longEnoughBytes, 3))
+                & 0x7FFF_FFFF;
+    }
+
+    private static int extractByte(byte[] bytes, int index) {
+        return (((int) bytes[index]) & 0xFF) << (index * 8);
+    }
+
+    /**
+     * @return whether a package should be logged, using a random value based on the ANDROID_ID,
+     * with a given sampling rate.
+     */
+    public static boolean shouldSamplePackageForAtom(String packageName, float rate) {
+        if (rate <= 0) {
+            return false;
+        }
+        if (rate >= 1) {
+            return true;
+        }
+        final int hash = getUnsignedHashCached(packageName) ^ getAndroidIdHash();
+
+        return (((double) hash) / Integer.MAX_VALUE) <= rate;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fae941d..774825e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -77,6 +77,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.net.LocalSocket;
@@ -4643,6 +4644,12 @@
                 }
             });
         }
+
+        // Update the global configuration and increase the assets sequence number.
+        Configuration currentConfig = mService.mActivityTaskManager.getConfiguration();
+        Configuration newConfig = new Configuration();
+        newConfig.assetsSeq = (currentConfig != null ? currentConfig.assetsSeq : 0) + 1;
+        mService.mActivityTaskManager.updateConfiguration(newConfig);
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 25b7add..d82847c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -146,9 +146,10 @@
         }
     }
 
-    public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException {
+    public void onFeatureGet(boolean success, int[] features, boolean[] featureState)
+            throws RemoteException {
         if (mFaceServiceReceiver != null) {
-            mFaceServiceReceiver.onFeatureGet(success, feature, value);
+            mFaceServiceReceiver.onFeatureGet(success, features, featureState);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index ca9be67..8726923 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -105,7 +105,7 @@
         }
 
         @Override
-        public void onFeatureGet(boolean success, int feature, boolean value) {
+        public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
 
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
new file mode 100644
index 0000000..12f3e87
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -0,0 +1,146 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.face.Feature;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Face-specific get feature client for the {@link IFace} AIDL HAL interface.
+ */
+public class FaceGetFeatureClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+
+    private static final String TAG = "FaceGetFeatureClient";
+
+    private final int mUserId;
+
+    FaceGetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+            @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
+            @NonNull String owner, int sensorId) {
+        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
+                BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mUserId = userId;
+    }
+
+    @Override
+    public void unableToStart() {
+        mCallback.onClientFinished(this, false /* success */);
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+        startHalOperation();
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon().getFeatures();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to getFeature", e);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_GET_FEATURE;
+    }
+
+    public void onFeatureGet(boolean success, byte[] features) {
+        HashMap<Integer, Boolean> featureMap = getFeatureMap();
+        int[] featuresToSend = new int[featureMap.size()];
+        boolean[] featureState = new boolean[featureMap.size()];
+
+        // The AIDL get feature api states that the presence of a feature means
+        // it is enabled, while the lack thereof means its disabled.
+        for (int i = 0; i < features.length; i++) {
+            Integer feature = convertAidlToFrameworkFeature(features[i]);
+            if (feature != null) {
+                featureMap.put(feature, true);
+            }
+        }
+
+        int i = 0;
+        for (Map.Entry<Integer, Boolean> entry : featureMap.entrySet()) {
+            featuresToSend[i] = entry.getKey();
+            featureState[i] = entry.getValue();
+            i++;
+        }
+
+        boolean attentionEnabled = featureMap.get(BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION);
+        Slog.d(TAG, "Updating attention value for user: " + mUserId
+                + " to value: " + attentionEnabled);
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
+                attentionEnabled ? 1 : 0, mUserId);
+        try {
+            getListener().onFeatureGet(success, featuresToSend, featureState);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+
+        mCallback.onClientFinished(this, true /* success */);
+    }
+
+    private @NonNull HashMap<Integer, Boolean> getFeatureMap() {
+        HashMap<Integer, Boolean> featureMap = new HashMap<>();
+        featureMap.put(BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, false);
+        return featureMap;
+    }
+
+    private Integer convertAidlToFrameworkFeature(byte feature) {
+        switch (feature) {
+            case Feature.REQUIRE_ATTENTION:
+                return new Integer(BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION);
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void onError(int errorCode, int vendorCode) {
+        try {
+            getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+
+        mCallback.onClientFinished(this, false /* success */);
+    }
+
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index b8bac40..23be50e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -435,13 +435,36 @@
     public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             boolean enabled, @NonNull byte[] hardwareAuthToken,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
-        // TODO(b/171335732): implement this.
+        mHandler.post(() -> {
+            final List<Face> faces = FaceUtils.getInstance(sensorId)
+                    .getBiometricsForUser(mContext, userId);
+            if (faces.isEmpty()) {
+                Slog.w(getTag(), "Ignoring setFeature, no templates enrolled for user: " + userId);
+                return;
+            }
+            final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token,
+                    new ClientMonitorCallbackConverter(receiver), userId,
+                    mContext.getOpPackageName(), sensorId, feature, enabled, hardwareAuthToken);
+            scheduleForSensor(sensorId, client);
+        });
     }
 
     @Override
     public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
-        // TODO(b/171335732): implement this.
+        mHandler.post(() -> {
+            final List<Face> faces = FaceUtils.getInstance(sensorId)
+                    .getBiometricsForUser(mContext, userId);
+            if (faces.isEmpty()) {
+                Slog.w(getTag(), "Ignoring getFeature, no templates enrolled for user: " + userId);
+                return;
+            }
+            final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
+                    mSensors.get(sensorId).getLazySession(), token, callback, userId,
+                    mContext.getOpPackageName(), sensorId);
+            scheduleForSensor(sensorId, client);
+        });
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
new file mode 100644
index 0000000..c3abfc2
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.face.Feature;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+/**
+ * Face-specific get feature client for the {@link IFace} AIDL HAL interface.
+ */
+public class FaceSetFeatureClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+
+    private static final String TAG = "FaceSetFeatureClient";
+
+    private final int mFeature;
+    private final boolean mEnabled;
+    private final HardwareAuthToken mHardwareAuthToken;
+
+    FaceSetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+            @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+            @NonNull String owner, int sensorId, int feature, boolean enabled,
+            byte[] hardwareAuthToken) {
+        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+                BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
+                BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mFeature = feature;
+        mEnabled = enabled;
+        mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+    }
+
+    @Override
+    public void unableToStart() {
+        try {
+            getListener().onFeatureSet(false /* success */, mFeature);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to send error", e);
+        }
+    }
+
+    @Override
+    public void start(@NonNull Callback callback) {
+        super.start(callback);
+        startHalOperation();
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon()
+                    .setFeature(mHardwareAuthToken,
+                    convertFrameworkToAidlFeature(mFeature), mEnabled);
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_SET_FEATURE;
+    }
+
+    public void onFeatureSet(boolean success) {
+        try {
+            getListener().onFeatureSet(success, mFeature);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+
+        mCallback.onClientFinished(this, true /* success */);
+    }
+
+    private byte convertFrameworkToAidlFeature(int feature) throws IllegalArgumentException {
+        switch (feature) {
+            case BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION:
+                return Feature.REQUIRE_ATTENTION;
+            default:
+                Slog.e(TAG, "Unsupported feature : " + feature);
+                throw new IllegalArgumentException();
+        }
+    }
+
+    @Override
+    public void onError(int errorCode, int vendorCode) {
+        try {
+            getListener().onFeatureSet(false /* success */, mFeature);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+
+        mCallback.onClientFinished(this, false /* success */);
+    }
+
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index b11bc87..724531e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -377,12 +377,32 @@
 
         @Override
         public void onFeaturesRetrieved(byte[] features) {
+            mHandler.post(() -> {
+                final BaseClientMonitor client = mScheduler.getCurrentClient();
+                if (!(client instanceof FaceGetFeatureClient)) {
+                    Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: "
+                            + Utils.getClientName(client));
+                    return;
+                }
+                final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client;
+                faceGetFeatureClient.onFeatureGet(true /* success */, features);
+            });
 
         }
 
         @Override
         public void onFeatureSet(byte feature) {
+            mHandler.post(() -> {
+                final BaseClientMonitor client = mScheduler.getCurrentClient();
+                if (!(client instanceof FaceSetFeatureClient)) {
+                    Slog.e(mTag, "onFeatureSet for non-set consumer: "
+                            + Utils.getClientName(client));
+                    return;
+                }
 
+                final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client;
+                faceSetFeatureClient.onFeatureSet(true /* success */);
+            });
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index e8668ed..f806767 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -94,7 +94,7 @@
         }
 
         @Override
-        public void onFeatureGet(boolean success, int feature, boolean value) {
+        public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
 
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index b1083d4..7821601 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -58,7 +58,7 @@
     public void unableToStart() {
         try {
             if (getListener() != null) {
-                getListener().onFeatureGet(false /* success */, mFeature, false /* value */);
+                getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to send error", e);
@@ -75,9 +75,14 @@
     protected void startHalOperation() {
         try {
             final OptionalBool result = getFreshDaemon().getFeature(mFeature, mFaceId);
+            int[] features = new int[1];
+            boolean[] featureState = new boolean[1];
+            features[0] = mFeature;
+            featureState[0] = result.value;
             mValue = result.value;
+
             if (getListener() != null) {
-                getListener().onFeatureGet(result.status == Status.OK, mFeature, mValue);
+                getListener().onFeatureGet(result.status == Status.OK, features, featureState);
             }
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index ace466a..2d7145f 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -695,6 +695,17 @@
         /** Minimum time that HBM can be on before being enabled. */
         public long timeMinMillis;
 
+        HighBrightnessModeData() {}
+
+        HighBrightnessModeData(float minimumLux, float transitionPoint,
+                long timeWindowMillis, long timeMaxMillis, long timeMinMillis) {
+            this.minimumLux = minimumLux;
+            this.transitionPoint = transitionPoint;
+            this.timeWindowMillis = timeWindowMillis;
+            this.timeMaxMillis = timeMaxMillis;
+            this.timeMinMillis = timeMinMillis;
+        }
+
         /**
          * Copies the HBM data to the specified parameter instance.
          * @param other the instance to copy data to.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 393a4eb..789f08f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -55,6 +55,7 @@
 import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
 import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
@@ -430,8 +431,8 @@
         mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
-        mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo,
-                new LogicalDisplayListener());
+        mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
+                new LogicalDisplayListener(), mSyncRoot, mHandler);
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
         mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
         Resources resources = mContext.getResources();
@@ -842,8 +843,6 @@
             return overriddenInfo;
         }
 
-
-
         return info;
     }
 
@@ -1269,7 +1268,7 @@
 
         DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
         if (dpc != null) {
-            dpc.onDisplayChangedLocked();
+            dpc.onDisplayChanged();
         }
     }
 
@@ -1305,12 +1304,23 @@
         handleLogicalDisplayChangedLocked(display);
     }
 
+    private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) {
+        final int displayId = display.getDisplayIdLocked();
+        final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+        if (dpc != null) {
+            dpc.onDeviceStateTransition();
+        }
+    }
+
     private Runnable updateDisplayStateLocked(DisplayDevice device) {
         // Blank or unblank the display immediately to match the state requested
         // by the display power controller (if known).
         DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
             final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
+            if (display == null) {
+                return null;
+            }
             final int displayId = display.getDisplayIdLocked();
             final int state = mDisplayStates.get(displayId);
 
@@ -1454,9 +1464,12 @@
         clearViewportsLocked();
 
         // Configure each display device.
-        mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
-            configureDisplayLocked(t, device);
-            device.performTraversalLocked(t);
+        mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
+            final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+            if (device != null) {
+                configureDisplayLocked(t, device);
+                device.performTraversalLocked(t);
+            }
         });
 
         // Tell the input system about these new viewports.
@@ -2062,10 +2075,16 @@
                 display, mContext);
         final DisplayPowerController displayPowerController = new DisplayPowerController(
                 mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager,
-                mDisplayBlanker, display, mBrightnessTracker, brightnessSetting);
+                mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
+                () -> handleBrightnessChange(display));
         mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
     }
 
+    private void handleBrightnessChange(LogicalDisplay display) {
+        sendDisplayEventLocked(display.getDisplayIdLocked(),
+                DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED);
+    }
+
     private final class DisplayManagerHandler extends Handler {
         public DisplayManagerHandler(Looper looper) {
             super(looper, null, true /*async*/);
@@ -2154,6 +2173,10 @@
                 case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED:
                     handleLogicalDisplayFrameRateOverridesChangedLocked(display);
                     break;
+
+                case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION:
+                    handleLogicalDisplayDeviceStateTransitionLocked(display);
+                    break;
             }
         }
 
@@ -2219,6 +2242,8 @@
                     return (mask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
                     return (mask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0;
+                case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
+                    return (mask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
                     return (mask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0;
                 default:
@@ -2781,6 +2806,25 @@
             }
         }
 
+        @Override
+        public BrightnessInfo getBrightnessInfo(int displayId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+                    "Permission required to read the display's brightness info.");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mSyncRoot) {
+                    DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+                    if (dpc != null) {
+                        return dpc.getBrightnessInfo();
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            return null;
+        }
+
         @Override // Binder call
         public boolean isMinimalPostProcessingRequested(int displayId) {
             synchronized (mSyncRoot) {
@@ -3232,4 +3276,17 @@
             }
         }
     };
+
+    /**
+     * Functional interface for providing time.
+     * TODO(b/184781936): merge with PowerManagerService.Clock
+     */
+    @VisibleForTesting
+    public interface Clock {
+        /**
+         * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+         */
+        long uptimeMillis();
+    }
+
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 86e4fd0..3340e3c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -32,6 +32,7 @@
 import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
 import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.metrics.LogMaker;
@@ -53,6 +54,7 @@
 import android.util.TimeUtils;
 import android.view.Display;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
@@ -146,6 +148,9 @@
     private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
     private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
 
+    private static final Uri BRIGHTNESS_FLOAT_URI =
+            Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT);
+
     private final Object mLock = new Object();
 
     private final Context mContext;
@@ -215,6 +220,9 @@
     // Whether or not the color fade on screen on / off is enabled.
     private final boolean mColorFadeEnabled;
 
+    @GuardedBy("mCachedBrightnessInfo")
+    private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo();
+
     // True if we should fade the screen while turning it off, false if we should play
     // a stylish color fade animation instead.
     private boolean mColorFadeFadesConfig;
@@ -359,6 +367,8 @@
 
     private final BrightnessSetting mBrightnessSetting;
 
+    private final Runnable mOnBrightnessChangeRunnable;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
@@ -428,7 +438,8 @@
     public DisplayPowerController(Context context,
             DisplayPowerCallbacks callbacks, Handler handler,
             SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
-            BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting) {
+            BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
+            Runnable onBrightnessChangeRunnable) {
         mLogicalDisplay = logicalDisplay;
         mDisplayId = mLogicalDisplay.getDisplayIdLocked();
         mHandler = new DisplayControllerHandler(handler.getLooper());
@@ -446,8 +457,9 @@
         mBlanker = blanker;
         mContext = context;
         mBrightnessTracker = brightnessTracker;
-
         mBrightnessSetting = brightnessSetting;
+        mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
+
         PowerManager pm = context.getSystemService(PowerManager.class);
 
         final Resources resources = context.getResources();
@@ -493,6 +505,9 @@
 
         mHbmController = createHbmController();
 
+        // Seed the cached brightness
+        saveBrightnessInfo(getScreenBrightnessSetting());
+
         if (mUseSoftwareAutoBrightnessConfig) {
             final float dozeScaleFactor = resources.getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
@@ -765,9 +780,8 @@
      * when displays get swapped on foldable devices.  For example, different brightness properties
      * of each display need to be properly reflected in AutomaticBrightnessController.
      */
-    public void onDisplayChangedLocked() {
+    public void onDisplayChanged() {
         // TODO: b/175821789 - Support high brightness on multiple (folding) displays
-
         mUniqueDisplayId = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
         mDisplayDeviceConfig = mLogicalDisplay.getPrimaryDisplayDeviceLocked()
                 .getDisplayDeviceConfig();
@@ -775,6 +789,15 @@
     }
 
     /**
+     * Called when the displays are preparing to transition from one device state to another.
+     * This process involves turning off some displays so we need updatePowerState() to run and
+     * calculate the new state.
+     */
+    public void onDeviceStateTransition() {
+        sendUpdatePowerState();
+    }
+
+    /**
      * Unregisters all listeners and interrupts all running threads; halting future work.
      *
      * This method should be called when the DisplayPowerController is no longer in use; i.e. when
@@ -1009,7 +1032,9 @@
             mIgnoreProximityUntilChanged = false;
         }
 
-        if (!mLogicalDisplay.isEnabled() || mScreenOffBecauseOfProximity) {
+        if (!mLogicalDisplay.isEnabled()
+                || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
+                || mScreenOffBecauseOfProximity) {
             state = Display.STATE_OFF;
         }
 
@@ -1163,6 +1188,10 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
+        // Save out the brightness info now that the brightness state for this iteration has been
+        // finalized and before we send out notifications about the brightness changing.
+        saveBrightnessInfo(brightnessState);
+
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
             // for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1395,13 +1424,36 @@
         msg.sendToTarget();
     }
 
+    public BrightnessInfo getBrightnessInfo() {
+        synchronized (mCachedBrightnessInfo) {
+            return new BrightnessInfo(
+                    mCachedBrightnessInfo.brightness,
+                    mCachedBrightnessInfo.brightnessMin,
+                    mCachedBrightnessInfo.brightnessMax,
+                    mCachedBrightnessInfo.hbmMode);
+        }
+    }
+
+    private void saveBrightnessInfo(float brightness) {
+        synchronized (mCachedBrightnessInfo) {
+            mCachedBrightnessInfo.brightness = brightness;
+            mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin();
+            mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax();
+            mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode();
+        }
+    }
+
     private HighBrightnessModeController createHbmController() {
         final DisplayDeviceConfig ddConfig =
                 mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
         return new HighBrightnessModeController(mHandler, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, hbmData, () -> sendUpdatePowerStateLocked());
+                PowerManager.BRIGHTNESS_MAX, hbmData,
+                () -> {
+                    sendUpdatePowerStateLocked();
+                    mHandler.post(mOnBrightnessChangeRunnable);
+                });
     }
 
     private void blockScreenOn() {
@@ -1832,7 +1884,7 @@
         mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
         if (userSwitch) {
             // Don't treat user switches as user initiated change.
-            mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
+            setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.resetShortTermModel();
             }
@@ -1871,11 +1923,18 @@
 
     private void putScreenBrightnessSetting(float brightnessValue, boolean updateCurrent) {
         if (updateCurrent) {
-            mCurrentScreenBrightnessSetting = brightnessValue;
+            setCurrentScreenBrightness(brightnessValue);
         }
         mBrightnessSetting.setBrightness(brightnessValue);
     }
 
+    private void setCurrentScreenBrightness(float brightnessValue) {
+        if (brightnessValue != mCurrentScreenBrightnessSetting) {
+            mCurrentScreenBrightnessSetting = brightnessValue;
+            mHandler.post(mOnBrightnessChangeRunnable);
+        }
+    }
+
     private void putAutoBrightnessAdjustmentSetting(float adjustment) {
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             mAutoBrightnessAdjustment = adjustment;
@@ -1908,7 +1967,7 @@
             mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             return false;
         }
-        mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
+        setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
         mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
         mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -2051,10 +2110,10 @@
         pw.println("  mPendingProximityDebounceTime="
                 + TimeUtils.formatUptime(mPendingProximityDebounceTime));
         pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
-        pw.println("  mLastUserSetScreenBrightnessFloat=" + mLastUserSetScreenBrightness);
-        pw.println("  mPendingScreenBrightnessSettingFloat="
+        pw.println("  mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
+        pw.println("  mPendingScreenBrightnessSetting="
                 + mPendingScreenBrightnessSetting);
-        pw.println("  mTemporaryScreenBrightnessFloat=" + mTemporaryScreenBrightness);
+        pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
         pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
@@ -2097,6 +2156,10 @@
             mAutomaticBrightnessController.dump(pw);
         }
 
+        if (mHbmController != null) {
+            mHbmController.dump(pw);
+        }
+
         pw.println();
         if (mDisplayWhiteBalanceController != null) {
             mDisplayWhiteBalanceController.dump(pw);
@@ -2425,4 +2488,11 @@
             }
         }
     }
+
+    static class CachedBrightnessInfo {
+        public float brightness;
+        public float brightnessMin;
+        public float brightnessMax;
+        public int hbmMode;
+    }
 }
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 2e5561d..e6486bd 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -16,13 +16,17 @@
 
 package com.android.server.display;
 
+import android.hardware.display.BrightnessInfo;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
+import com.android.server.display.DisplayManagerService.Clock;
 
+import java.io.PrintWriter;
 import java.util.Iterator;
 import java.util.LinkedList;
 
@@ -45,11 +49,13 @@
     private final Handler mHandler;
     private final Runnable mHbmChangeCallback;
     private final Runnable mRecalcRunnable;
+    private final Clock mClock;
 
     private boolean mIsInAllowedAmbientRange = false;
     private boolean mIsTimeAvailable = false;
     private boolean mIsAutoBrightnessEnabled = false;
     private float mAutoBrightness;
+    private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
 
     /**
      * If HBM is currently running, this is the start time for the current HBM session.
@@ -65,28 +71,25 @@
 
     HighBrightnessModeController(Handler handler, float brightnessMin, float brightnessMax,
             HighBrightnessModeData hbmData, Runnable hbmChangeCallback) {
+        this(SystemClock::uptimeMillis, handler, brightnessMin, brightnessMax, hbmData,
+                hbmChangeCallback);
+    }
+
+    @VisibleForTesting
+    HighBrightnessModeController(Clock clock, Handler handler, float brightnessMin,
+            float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback) {
+        mClock = clock;
         mHandler = handler;
         mBrightnessMin = brightnessMin;
         mBrightnessMax = brightnessMax;
         mHbmData = hbmData;
         mHbmChangeCallback = hbmChangeCallback;
         mAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-
-        mRecalcRunnable = () -> {
-            boolean oldIsAllowed = isCurrentlyAllowed();
-            recalculateTimeAllowance();
-            if (oldIsAllowed != isCurrentlyAllowed()) {
-                // Our allowed state has changed; tell AutomaticBrightnessController
-                // to update the brightness.
-                if (mHbmChangeCallback != null) {
-                    mHbmChangeCallback.run();
-                }
-            }
-        };
+        mRecalcRunnable = this::recalculateTimeAllowance;
     }
 
     void setAutoBrightnessEnabled(boolean isEnabled) {
-        if (isEnabled == mIsAutoBrightnessEnabled) {
+        if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) {
             return;
         }
         if (DEBUG) {
@@ -94,6 +97,7 @@
         }
         mIsAutoBrightnessEnabled = isEnabled;
         mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
+        recalculateTimeAllowance();
     }
 
     float getCurrentBrightnessMin() {
@@ -137,7 +141,7 @@
         final boolean wasOldBrightnessHigh = oldAutoBrightness > mHbmData.transitionPoint;
         final boolean isNewBrightnessHigh = mAutoBrightness > mHbmData.transitionPoint;
         if (wasOldBrightnessHigh != isNewBrightnessHigh) {
-            final long currentTime = SystemClock.uptimeMillis();
+            final long currentTime = mClock.uptimeMillis();
             if (isNewBrightnessHigh) {
                 mRunningStartTimeMillis = currentTime;
             } else {
@@ -153,6 +157,21 @@
         recalculateTimeAllowance();
     }
 
+    int getHighBrightnessMode() {
+        return mHbmMode;
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("HighBrightnessModeController:");
+        pw.println("  mBrightnessMin=" + mBrightnessMin);
+        pw.println("  mBrightnessMax=" + mBrightnessMax);
+        pw.println("  mHbmData=" + mHbmData);
+        pw.println("  mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
+        pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
+        pw.println("  mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
+        pw.println("  mAutoBrightness=" + mAutoBrightness);
+    }
+
     private boolean isCurrentlyAllowed() {
         return mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange;
     }
@@ -165,7 +184,7 @@
      * Recalculates the allowable HBM time.
      */
     private void recalculateTimeAllowance() {
-        final long currentTime = SystemClock.uptimeMillis();
+        final long currentTime = mClock.uptimeMillis();
         long timeAlreadyUsed = 0;
 
         // First, lets see how much time we've taken for any currently running
@@ -247,8 +266,22 @@
 
         if (nextTimeout != -1) {
             mHandler.removeCallbacks(mRecalcRunnable);
-            mHandler.postAtTime(mRecalcRunnable, nextTimeout);
+            mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1);
         }
+
+        // Update the state of the world
+        int newHbmMode = calculateHighBrightnessMode();
+        if (mHbmMode != newHbmMode) {
+            mHbmMode = newHbmMode;
+            mHbmChangeCallback.run();
+        }
+    }
+
+    private int calculateHighBrightnessMode() {
+        if (deviceSupportsHbm() && isCurrentlyAllowed()) {
+            return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+        }
+        return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 1589419..9acb4c8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Point;
@@ -65,6 +66,33 @@
 final class LogicalDisplay {
     private static final String TAG = "LogicalDisplay";
 
+    /**
+     * Phase indicating the logical display's existence is hidden from the rest of the framework.
+     * This can happen if the current layout has specifically requested to keep this display
+     * disabled.
+     */
+    static final int DISPLAY_PHASE_DISABLED = -1;
+
+    /**
+     * Phase indicating that the logical display is going through a layout transition.
+     * When in this phase, other systems can choose to special case power-state handling of a
+     * display that might be in a transition.
+     */
+    static final int DISPLAY_PHASE_LAYOUT_TRANSITION = 0;
+
+    /**
+     * The display is exposed to the rest of the system and its power state is determined by a
+     * power-request from PowerManager.
+     */
+    static final int DISPLAY_PHASE_ENABLED = 1;
+
+    @IntDef(prefix = {"DISPLAY_PHASE" }, value = {
+        DISPLAY_PHASE_DISABLED,
+        DISPLAY_PHASE_LAYOUT_TRANSITION,
+        DISPLAY_PHASE_ENABLED
+    })
+    @interface DisplayPhase {}
+
     // The layer stack we use when the display has been blanked to prevent any
     // of its content from appearing.
     private static final int BLANK_LAYER_STACK = -1;
@@ -129,10 +157,12 @@
     private final Rect mTempDisplayRect = new Rect();
 
     /**
-     * Indicates that the Logical display is enabled (default). See {@link #setEnabled} for
-     * more information.
+     * Indicates the current phase of the display. Generally, phases supersede any
+     * requests from PowerManager in DPC's calculation for the display state. Only when the
+     * phase is ENABLED does PowerManager's request for the display take effect.
      */
-    private boolean mIsEnabled = true;
+    @DisplayPhase
+    private int mPhase = DISPLAY_PHASE_ENABLED;
 
     /**
      * The UID mappings for refresh rate override
@@ -721,27 +751,32 @@
         return old;
     }
 
-    /**
-     * Sets the LogicalDisplay to be enabled or disabled. If the display is not enabled,
-     * the system will always set the display to power off, regardless of the global state of the
-     * device.
-     * TODO: b/170498827 - Remove when updateDisplayStateLocked is updated.
-     */
-    public void setEnabled(boolean isEnabled) {
-        mIsEnabled = isEnabled;
+    public void setPhase(@DisplayPhase int phase) {
+        mPhase = phase;
     }
 
     /**
-     * @return {@code true} iff the LogicalDisplay is enabled or {@code false}
-     * if disabled indicating that the display has been forced to be OFF.
+     * Returns the currently set phase for this LogicalDisplay. Phases are used when transitioning
+     * from one device state to another. {@see LogicalDisplayMapper}.
+     */
+    @DisplayPhase
+    public int getPhase() {
+        return mPhase;
+    }
+
+    /**
+     * @return {@code true} if the LogicalDisplay is enabled or {@code false}
+     * if disabled indicating that the display should be hidden from the rest of the apps and
+     * framework.
      */
     public boolean isEnabled() {
-        return mIsEnabled;
+        // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase.
+        return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION;
     }
 
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
-        pw.println("mIsEnabled=" + mIsEnabled);
+        pw.println("mPhase=" + mPhase);
         pw.println("mLayerStack=" + mLayerStack);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index fcfa674..4c9a2d7 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -16,18 +16,24 @@
 
 package com.android.server.display;
 
+import android.annotation.NonNull;
+import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.LogicalDisplay.DisplayPhase;
 import com.android.server.display.layout.Layout;
 
 import java.io.PrintWriter;
@@ -55,11 +61,20 @@
     public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3;
     public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
     public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
+    public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 6;
 
     public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
     public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
     public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
 
+    private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 500;
+
+    private static final int MSG_TRANSITION_TO_PENDING_DEVICE_STATE = 1;
+
+    private static final int UPDATE_STATE_NEW = 0;
+    private static final int UPDATE_STATE_TRANSITION = 1;
+    private static final int UPDATE_STATE_UPDATED = 2;
+
     /**
      * Temporary display info, used for comparing display configurations.
      */
@@ -76,6 +91,11 @@
     private final boolean mSingleDisplayDemoMode;
 
     /**
+     * True if the device can have more than one internal display on at a time.
+     */
+    private final boolean mSupportsConcurrentInternalDisplays;
+
+    /**
      * Map of all logical displays indexed by logical display id.
      * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
      * TODO: multi-display - Move the aforementioned comment?
@@ -89,13 +109,16 @@
     private final DisplayDeviceRepository mDisplayDeviceRepo;
     private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
     private final Listener mListener;
+    private final DisplayManagerService.SyncRoot mSyncRoot;
+    private final LogicalDisplayMapperHandler mHandler;
 
     /**
      * Has an entry for every logical display that the rest of the system has been notified about.
      * Any entry in here requires us to send a {@link  LOGICAL_DISPLAY_EVENT_REMOVED} event when it
-     * is deleted or {@link  LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed.
+     * is deleted or {@link  LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. The values are any
+     * of the {@code UPDATE_STATE_*} constant types.
      */
-    private final SparseBooleanArray mUpdatedLogicalDisplays = new SparseBooleanArray();
+    private final SparseIntArray mUpdatedLogicalDisplays = new SparseIntArray();
 
     /**
      * Keeps track of all the display groups that we already told other people about. IOW, if a
@@ -119,11 +142,18 @@
     private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
     private Layout mCurrentLayout = null;
     private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+    private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
 
-    LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener) {
+    LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
+            @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
+            @NonNull Handler handler) {
+        mSyncRoot = syncRoot;
+        mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
         mDisplayDeviceRepo = repo;
         mListener = listener;
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
+        mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
         mDisplayDeviceRepo.addListener(this);
         mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
     }
@@ -142,6 +172,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
                 }
+                finishStateTransitionLocked(false /*force*/);
                 updateLogicalDisplaysLocked();
                 break;
 
@@ -166,7 +197,7 @@
     public LogicalDisplay getDisplayLocked(DisplayDevice device) {
         final int count = mLogicalDisplays.size();
         for (int i = 0; i < count; i++) {
-            LogicalDisplay display = mLogicalDisplays.valueAt(i);
+            final LogicalDisplay display = mLogicalDisplays.valueAt(i);
             if (display.getPrimaryDisplayDeviceLocked() == device) {
                 return display;
             }
@@ -198,6 +229,7 @@
         }
     }
 
+    @VisibleForTesting
     public int getDisplayGroupIdFromDisplayIdLocked(int displayId) {
         final LogicalDisplay display = getDisplayLocked(displayId);
         if (display == null) {
@@ -225,7 +257,6 @@
         ipw.increaseIndent();
 
         ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
-
         ipw.println("mCurrentLayout=" + mCurrentLayout);
 
         final int logicalDisplayCount = mLogicalDisplays.size();
@@ -244,19 +275,78 @@
     }
 
     void setDeviceStateLocked(int state) {
-        if (state != mDeviceState) {
-            resetLayoutLocked();
-            mDeviceState = state;
-            applyLayoutLocked();
-            updateLogicalDisplaysLocked();
+        Slog.i(TAG, "Requesting Transition to state: " + state);
+        // As part of a state transition, we may need to turn off some displays temporarily so that
+        // the transition is smooth. Plus, on some devices, only one internal displays can be
+        // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be
+        // temporarily turned off.
+        if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
+            resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
+        }
+        mPendingDeviceState = state;
+        if (areAllTransitioningDisplaysOffLocked()) {
+            // Nothing to wait on, we're good to go
+            transitionToPendingStateLocked();
+            return;
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState);
+        }
+        // Send the transitioning phase updates to DisplayManager so that the displays can
+        // start turning OFF in preparation for the new layout.
+        updateLogicalDisplaysLocked();
+        mHandler.sendEmptyMessageDelayed(MSG_TRANSITION_TO_PENDING_DEVICE_STATE,
+                TIMEOUT_STATE_TRANSITION_MILLIS);
+    }
+
+    private boolean areAllTransitioningDisplaysOffLocked() {
+        final int count = mLogicalDisplays.size();
+        for (int i = 0; i < count; i++) {
+            final LogicalDisplay display = mLogicalDisplays.valueAt(i);
+            if (display.getPhase() != LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
+                continue;
+            }
+
+            final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+            if (device != null) {
+                final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+                if (info.state != Display.STATE_OFF) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void transitionToPendingStateLocked() {
+        resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        mDeviceState = mPendingDeviceState;
+        mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+        applyLayoutLocked();
+        updateLogicalDisplaysLocked();
+    }
+
+    private void finishStateTransitionLocked(boolean force) {
+        if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE) {
+            return;
+        }
+
+        final boolean displaysOff = areAllTransitioningDisplaysOffLocked();
+        if (displaysOff || force) {
+            transitionToPendingStateLocked();
+            mHandler.removeMessages(MSG_TRANSITION_TO_PENDING_DEVICE_STATE);
+        } else if (DEBUG) {
+            Slog.d(TAG, "Not yet ready to transition to state=" + mPendingDeviceState
+                    + " with displays-off=" + displaysOff + " and force=" + force);
         }
     }
 
     private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
         DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
         // Internal Displays need to have additional initialization.
-        // TODO: b/168208162 - This initializes a default dynamic display layout for INTERNAL
-        // devices, which will eventually just be a fallback in case no static layout definitions
+        // This initializes a default dynamic display layout for INTERNAL
+        // devices, which is used as a fallback in case no static layout definitions
         // exist or cannot be loaded.
         if (deviceInfo.type == Display.TYPE_INTERNAL) {
             initializeInternalDisplayDeviceLocked(device);
@@ -289,7 +379,8 @@
 
             display.updateLocked(mDisplayDeviceRepo);
             final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();
-            final boolean wasPreviouslyUpdated = mUpdatedLogicalDisplays.get(displayId);
+            final int updateState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW);
+            final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW;
 
             // The display is no longer valid and needs to be removed.
             if (!display.isValidLocked()) {
@@ -331,6 +422,10 @@
                 assignDisplayGroupLocked(display);
                 mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
 
+            } else if (updateState == UPDATE_STATE_TRANSITION) {
+                mLogicalDisplaysToUpdate.put(displayId,
+                        LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
+
             // Display frame rate overrides changed.
             } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
                 mLogicalDisplaysToUpdate.put(
@@ -347,7 +442,7 @@
                 }
             }
 
-            mUpdatedLogicalDisplays.put(displayId, true);
+            mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);
         }
 
         // Go through the groups and do the same thing. We do this after displays since group
@@ -376,12 +471,13 @@
 
         // Send the display and display group updates in order by message type. This is important
         // to ensure that addition and removal notifications happen in the right order.
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
         sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
-        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
         sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
         sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED);
 
@@ -400,7 +496,14 @@
             }
 
             final int id = mLogicalDisplaysToUpdate.keyAt(i);
-            mListener.onLogicalDisplayEventLocked(getDisplayLocked(id), msg);
+            final LogicalDisplay display = getDisplayLocked(id);
+            if (DEBUG) {
+                final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+                final String uniqueId = device == null ? "null" : device.getUniqueId();
+                Slog.d(TAG, "Sending " + displayEventToString(msg) + " for display=" + id
+                        + " with device=" + uniqueId);
+            }
+            mListener.onLogicalDisplayEventLocked(display, msg);
             if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
                 // We wait until we sent the EVENT_REMOVED event before actually removing the
                 // display.
@@ -464,36 +567,81 @@
     }
 
     /**
-     * Resets the current layout in preparation for a new layout. Layouts can specify if some
-     * displays should be disabled (OFF). When switching from one layout to another, we go
-     * through each of the displays and make sure any displays we might have disabled are
-     * enabled again.
+     * Goes through all the displays used in the layouts for the specified {@code fromState} and
+     * {@code toState} and applies the specified {@code phase}. When a new layout is requested, we
+     * put the displays that will change into a transitional phase so that they can all be turned
+     * OFF. Once all are confirmed OFF, then this method gets called again to reset the phase to
+     * normal operation. This helps to ensure that all display-OFF requests are made before
+     * display-ON which in turn hides any resizing-jank windows might incur when switching displays.
+     *
+     * @param fromState The state we are switching from.
+     * @param toState The state we are switching to.
+     * @param phase The new phase to apply to the displays.
      */
-    private void resetLayoutLocked() {
-        final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState);
-        for (int i = layout.size() - 1; i >= 0; i--) {
-            final Layout.Display displayLayout = layout.getAt(i);
-            final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId());
-            if (display != null) {
-                enableDisplayLocked(display, true); // Reset all displays back to enabled
+    private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) {
+        final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState);
+        final Layout toLayout = mDeviceStateToLayoutMap.get(toState);
+
+        final int count = mLogicalDisplays.size();
+        for (int i = 0; i < count; i++) {
+            final LogicalDisplay logicalDisplay = mLogicalDisplays.valueAt(i);
+            final int displayId = logicalDisplay.getDisplayIdLocked();
+            final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+            if (device == null) {
+                // If there's no device, then the logical display is due to be removed. Ignore it.
+                continue;
+            }
+
+            // Grab the display associations this display-device has in the old layout and the
+            // new layout.
+            final DisplayAddress address = device.getDisplayDeviceInfoLocked().address;
+
+            // Virtual displays do not have addresses.
+            final Layout.Display fromDisplay =
+                    address != null ? fromLayout.getByAddress(address) : null;
+            final Layout.Display toDisplay =
+                    address != null ? toLayout.getByAddress(address) : null;
+
+            // If a layout doesn't mention a display-device at all, then the display-device defaults
+            // to enabled. This is why we treat null as "enabled" in the code below.
+            final boolean wasEnabled = fromDisplay == null || fromDisplay.isEnabled();
+            final boolean willBeEnabled = toDisplay == null || toDisplay.isEnabled();
+
+            final boolean deviceHasNewLogicalDisplayId = fromDisplay != null && toDisplay != null
+                    && fromDisplay.getLogicalDisplayId() != toDisplay.getLogicalDisplayId();
+
+            // We consider a display-device as changing/transition if
+            // 1) It's already marked as transitioning
+            // 2) It's going from enabled to disabled
+            // 3) It's enabled, but it's mapped to a new logical display ID. To the user this
+            //    would look like apps moving from one screen to another since task-stacks stay
+            //    with the logical display [ID].
+            final boolean isTransitioning =
+                    (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION)
+                    || (wasEnabled && !willBeEnabled)
+                    || (wasEnabled && deviceHasNewLogicalDisplayId);
+
+            if (isTransitioning) {
+                setDisplayPhase(logicalDisplay, phase);
+                if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
+                    mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION);
+                }
             }
         }
     }
 
-
     /**
      * Apply (or reapply) the currently selected display layout.
      */
     private void applyLayoutLocked() {
-        final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState);
-        mCurrentLayout = layout;
-        Slog.i(TAG, "Applying the display layout for device state(" + mDeviceState
-                + "): " + layout);
+        final Layout oldLayout = mCurrentLayout;
+        mCurrentLayout = mDeviceStateToLayoutMap.get(mDeviceState);
+        Slog.i(TAG, "Applying layout: " + mCurrentLayout + ", Previous layout: " + oldLayout);
 
         // Go through each of the displays in the current layout set.
-        final int size = layout.size();
+        final int size = mCurrentLayout.size();
         for (int i = 0; i < size; i++) {
-            final Layout.Display displayLayout = layout.getAt(i);
+            final Layout.Display displayLayout = mCurrentLayout.getAt(i);
 
             // If the underlying display-device we want to use for this display
             // doesn't exist, then skip it. This can happen at startup as display-devices
@@ -521,8 +669,12 @@
             if (newDisplay != oldDisplay) {
                 newDisplay.swapDisplaysLocked(oldDisplay);
             }
-            enableDisplayLocked(newDisplay, displayLayout.isEnabled());
+
+            if (!displayLayout.isEnabled()) {
+                setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED);
+            }
         }
+
     }
 
 
@@ -540,23 +692,23 @@
         final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
         display.updateLocked(mDisplayDeviceRepo);
         mLogicalDisplays.put(displayId, display);
-        enableDisplayLocked(display, device != null);
+        setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);
         return display;
     }
 
-    private void enableDisplayLocked(LogicalDisplay display, boolean isEnabled) {
+    private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) {
         final int displayId = display.getDisplayIdLocked();
         final DisplayInfo info = display.getDisplayInfoLocked();
 
         final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode
                 && (info.type != Display.TYPE_INTERNAL);
-        if (isEnabled && disallowSecondaryDisplay) {
+        if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) {
             Slog.i(TAG, "Not creating a logical display for a secondary display because single"
                     + " display demo mode is enabled: " + display.getDisplayInfoLocked());
-            isEnabled = false;
+            phase = LogicalDisplay.DISPLAY_PHASE_DISABLED;
         }
 
-        display.setEnabled(isEnabled);
+        display.setPhase(phase);
     }
 
     private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) {
@@ -564,14 +716,15 @@
     }
 
     private void initializeInternalDisplayDeviceLocked(DisplayDevice device) {
-        // We always want to make sure that our default display layout creates a logical
+        // We always want to make sure that our default layout creates a logical
         // display for every internal display device that is found.
         // To that end, when we are notified of a new internal display, we add it to
-        // the default definition if it is not already there.
-        final Layout layoutSet = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT);
+        // the default layout definition if it is not already there.
+        final Layout layout = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT);
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         final boolean isDefault = (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
-        layoutSet.createDisplayLocked(info.address, isDefault, true /* isEnabled */);
+        final boolean isEnabled = isDefault || mSupportsConcurrentInternalDisplays;
+        layout.createDisplayLocked(info.address, isDefault, isEnabled);
     }
 
     private int assignLayerStackLocked(int displayId) {
@@ -580,9 +733,45 @@
         return displayId;
     }
 
+    private String displayEventToString(int msg) {
+        switch(msg) {
+            case LOGICAL_DISPLAY_EVENT_ADDED:
+                return "added";
+            case LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION:
+                return "transition";
+            case LOGICAL_DISPLAY_EVENT_CHANGED:
+                return "changed";
+            case LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED:
+                return "framerate_override";
+            case LOGICAL_DISPLAY_EVENT_SWAPPED:
+                return "swapped";
+            case LOGICAL_DISPLAY_EVENT_REMOVED:
+                return "removed";
+        }
+        return null;
+    }
+
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
         void onDisplayGroupEventLocked(int groupId, int event);
         void onTraversalRequested();
     }
+
+    private class LogicalDisplayMapperHandler extends Handler {
+        LogicalDisplayMapperHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_TRANSITION_TO_PENDING_DEVICE_STATE:
+                    synchronized (mSyncRoot) {
+                        finishStateTransitionLocked(true /*force*/);
+                    }
+                    break;
+            }
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index ef33667..e53aec1 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -19,6 +19,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Slog;
 import android.view.DisplayAddress;
 
@@ -100,11 +101,28 @@
      *
      * @return The display corresponding to the specified display ID.
      */
+    @Nullable
     public Display getById(int id) {
         for (int i = 0; i < mDisplays.size(); i++) {
-            Display layout = mDisplays.get(i);
-            if (id == layout.getLogicalDisplayId()) {
-                return layout;
+            Display display = mDisplays.get(i);
+            if (id == display.getLogicalDisplayId()) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param address The display address to check.
+     *
+     * @return The display corresponding to the specified address.
+     */
+    @Nullable
+    public Display getByAddress(@NonNull DisplayAddress address) {
+        for (int i = 0; i < mDisplays.size(); i++) {
+            Display display = mDisplays.get(i);
+            if (address.equals(display.getAddress())) {
+                return display;
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 2bf74c9..5802e53 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -232,7 +232,8 @@
                 DEST_DIRECT);
 
         // Messages for Feature Discovery.
-        addValidationInfo(Constants.MESSAGE_GIVE_FEATURES, noneValidator, DEST_DIRECT);
+        addValidationInfo(Constants.MESSAGE_GIVE_FEATURES, noneValidator,
+                DEST_DIRECT | SRC_UNREGISTERED);
         addValidationInfo(Constants.MESSAGE_REPORT_FEATURES, new VariableLengthValidator(4, 14),
                 DEST_BROADCAST);
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 754fa25..77de187 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -422,6 +422,9 @@
     // Set to true if the logical address allocation is completed.
     private boolean mAddressAllocated = false;
 
+    // Whether a CEC-enabled sink is connected to the playback device
+    private boolean mIsCecAvailable = false;
+
     // Object that handles logging statsd atoms.
     // Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing.
     private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter();
@@ -2229,6 +2232,7 @@
 
             pw.println("mProhibitMode: " + mProhibitMode);
             pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
+            pw.println("mIsCecAvailable: " + mIsCecAvailable);
             pw.println("mCecVersion: " + mCecVersion);
 
             // System settings
@@ -2450,7 +2454,7 @@
         if (hdmiCecEnabled != HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
             return false;
         }
-        return true;
+        return mIsCecAvailable;
     }
 
     @ServiceThreadOnly
@@ -2835,24 +2839,24 @@
     private void invokeHdmiControlStatusChangeListenerLocked(
             Collection<IHdmiControlStatusChangeListener> listeners,
             @HdmiControlManager.HdmiCecControl int isEnabled) {
-        if (listeners.isEmpty()) {
-            return;
-        }
         if (isEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
             queryDisplayStatus(new IHdmiControlCallback.Stub() {
                 public void onComplete(int status) {
-                    boolean isAvailable = true;
                     if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
                             || status == HdmiControlManager.RESULT_EXCEPTION
                             || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
-                        isAvailable = false;
+                        mIsCecAvailable = false;
+                    } else {
+                        mIsCecAvailable = true;
                     }
-                    invokeHdmiControlStatusChangeListenerLocked(listeners, isEnabled, isAvailable);
                 }
             });
-            return;
+        } else {
+            mIsCecAvailable = false;
         }
-        invokeHdmiControlStatusChangeListenerLocked(listeners, isEnabled, false);
+        if (!listeners.isEmpty()) {
+            invokeHdmiControlStatusChangeListenerLocked(listeners, isEnabled, mIsCecAvailable);
+        }
     }
 
     private void invokeHdmiControlStatusChangeListenerLocked(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3424821..c7c681b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3355,68 +3355,100 @@
 
     @NonNull
     @Override
+    public void reportWindowGainedFocusAsync(
+            boolean nextFocusHasConnection, IInputMethodClient client, IBinder windowToken,
+            @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+            int windowFlags, int unverifiedTargetSdkVersion) {
+        final int startInputReason = nextFocusHasConnection
+                ? StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+                : StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+        try {
+            startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+                    startInputFlags, softInputMode, windowFlags, null /* attribute */,
+                    null /* inputContext */, 0 /* missingMethods */, unverifiedTargetSdkVersion);
+        } catch (Throwable t) {
+            if (client != null) {
+                try {
+                    client.throwExceptionFromSystem(t.getMessage());
+                } catch (RemoteException ignore) { }
+            }
+        }
+    }
+
+    @NonNull
+    @Override
     public void startInputOrWindowGainedFocus(
             @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
             @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
             int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
             @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion,
             IInputBindResultResultCallback resultCallback) {
-        CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () -> {
-            if (windowToken == null) {
-                Slog.e(TAG, "windowToken cannot be null.");
+        CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () ->
+                startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+                startInputFlags, softInputMode, windowFlags, attribute, inputContext,
+                missingMethods, unverifiedTargetSdkVersion));
+    }
+
+    @NonNull
+    private InputBindResult startInputOrWindowGainedFocusInternal(
+            @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+            @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+            int windowFlags, @Nullable EditorInfo attribute, @Nullable IInputContext inputContext,
+            @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) {
+        if (windowToken == null) {
+            Slog.e(TAG, "windowToken cannot be null.");
+            return InputBindResult.NULL;
+        }
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                    "IMMS.startInputOrWindowGainedFocus");
+            ImeTracing.getInstance().triggerManagerServiceDump(
+                    "InputMethodManagerService#startInputOrWindowGainedFocus");
+            final int callingUserId = UserHandle.getCallingUserId();
+            final int userId;
+            if (attribute != null && attribute.targetInputMethodUser != null
+                    && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
+                mContext.enforceCallingPermission(
+                        Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        "Using EditorInfo.targetInputMethodUser requires"
+                                + " INTERACT_ACROSS_USERS_FULL.");
+                userId = attribute.targetInputMethodUser.getIdentifier();
+                if (!mUserManagerInternal.isUserRunning(userId)) {
+                    // There is a chance that we hit here because of race condition. Let's just
+                    // return an error code instead of crashing the caller process, which at
+                    // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+                    // important process.
+                    Slog.e(TAG, "User #" + userId + " is not running.");
+                    return InputBindResult.INVALID_USER;
+                }
+            } else {
+                userId = callingUserId;
+            }
+            final InputBindResult result;
+            synchronized (mMethodMap) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
+                            client, windowToken, startInputFlags, softInputMode, windowFlags,
+                            attribute, inputContext, missingMethods, unverifiedTargetSdkVersion,
+                            userId);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+            if (result == null) {
+                // This must never happen, but just in case.
+                Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
+                        + InputMethodDebug.startInputReasonToString(startInputReason)
+                        + " windowFlags=#" + Integer.toHexString(windowFlags)
+                        + " editorInfo=" + attribute);
                 return InputBindResult.NULL;
             }
-            try {
-                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                        "IMMS.startInputOrWindowGainedFocus");
-                ImeTracing.getInstance().triggerManagerServiceDump(
-                        "InputMethodManagerService#startInputOrWindowGainedFocus");
-                final int callingUserId = UserHandle.getCallingUserId();
-                final int userId;
-                if (attribute != null && attribute.targetInputMethodUser != null
-                        && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
-                    mContext.enforceCallingPermission(
-                            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                            "Using EditorInfo.targetInputMethodUser requires"
-                                    + " INTERACT_ACROSS_USERS_FULL.");
-                    userId = attribute.targetInputMethodUser.getIdentifier();
-                    if (!mUserManagerInternal.isUserRunning(userId)) {
-                        // There is a chance that we hit here because of race condition. Let's just
-                        // return an error code instead of crashing the caller process, which at
-                        // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
-                        // important process.
-                        Slog.e(TAG, "User #" + userId + " is not running.");
-                        return InputBindResult.INVALID_USER;
-                    }
-                } else {
-                    userId = callingUserId;
-                }
-                final InputBindResult result;
-                synchronized (mMethodMap) {
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
-                                client, windowToken, startInputFlags, softInputMode, windowFlags,
-                                attribute, inputContext, missingMethods, unverifiedTargetSdkVersion,
-                                userId);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-                if (result == null) {
-                    // This must never happen, but just in case.
-                    Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
-                            + InputMethodDebug.startInputReasonToString(startInputReason)
-                            + " windowFlags=#" + Integer.toHexString(windowFlags)
-                            + " editorInfo=" + attribute);
-                    return InputBindResult.NULL;
-                }
 
-                return result;
-            } finally {
-                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-            }
-        });
+            return result;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index e25b03481..403187b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -221,7 +221,6 @@
      */
     @VisibleForTesting
     public Context getSettingsContext(int displayId) {
-        // TODO(b/178462039): Cover the case when IME is moved to another ImeContainer.
         if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
             final Context systemUiContext = ActivityThread.currentActivityThread()
                     .createSystemUiContext(displayId);
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index fcaf6d8..0fc91ba 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1627,6 +1627,33 @@
 
         @BinderThread
         @Override
+        public void reportWindowGainedFocusAsync(
+                boolean nextFocusHasConnection,
+                @Nullable IInputMethodClient client,
+                @Nullable IBinder windowToken,
+                @StartInputFlags int startInputFlags,
+                @SoftInputModeFlags int softInputMode,
+                int windowFlags,
+                int unverifiedTargetSdkVersion) {
+            final int startInputReason = nextFocusHasConnection
+                    ? StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+                    : StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+            try {
+                startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+                        startInputFlags, softInputMode, windowFlags, null /* editorInfo */,
+                        null /* inputContext */, 0 /* missingMethods */,
+                        unverifiedTargetSdkVersion);
+            } catch (Throwable t) {
+                if (client != null) {
+                    try {
+                        client.throwExceptionFromSystem(t.getMessage());
+                    } catch (RemoteException ignore) { }
+                }
+            }
+        }
+
+        @BinderThread
+        @Override
         public void startInputOrWindowGainedFocus(
                 @StartInputReason int startInputReason,
                 @Nullable IInputMethodClient client,
@@ -1641,8 +1668,8 @@
                 IInputBindResultResultCallback resultCallback) {
             CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () ->
                     startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
-                            startInputFlags, softInputMode, windowFlags, editorInfo, inputContext,
-                            missingMethods, unverifiedTargetSdkVersion));
+                    startInputFlags, softInputMode, windowFlags, editorInfo, inputContext,
+                    missingMethods, unverifiedTargetSdkVersion));
         }
 
         @BinderThread
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 6a5c2d89..a73c8e0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -311,7 +311,7 @@
             PasswordMetrics metrics = new PasswordMetrics(
                     credential.isPattern() ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_NONE);
             errors = PasswordMetrics.validatePasswordMetrics(
-                    requiredMetrics, requiredComplexity, false /* isPin */, metrics);
+                    requiredMetrics, requiredComplexity, metrics);
         }
         if (!errors.isEmpty()) {
             getOutPrintWriter().println(
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 4f527f2..cd352b5 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -35,6 +35,7 @@
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.content.pm.parsing.component.ParsedProvider;
+import android.os.Binder;
 import android.os.Process;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -51,6 +52,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.function.QuadFunction;
 import com.android.server.FgThread;
 import com.android.server.compat.CompatChange;
 import com.android.server.om.OverlayReferenceMapper;
@@ -69,7 +71,6 @@
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.concurrent.Executor;
-import java.util.function.Function;
 
 /**
  * The entity responsible for filtering visibility between apps based on declarations in their
@@ -1447,14 +1448,20 @@
 
     public void dumpQueries(
             PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState, int[] users,
-            Function<Integer, String[]> getPackagesForUid) {
+            QuadFunction<Integer, Integer, Integer, Boolean, String[]> getPackagesForUid) {
         final SparseArray<String> cache = new SparseArray<>();
         ToString<Integer> expandPackages = input -> {
             String cachedValue = cache.get(input);
             if (cachedValue == null) {
-                final String[] packagesForUid = getPackagesForUid.apply(input);
+                final int callingUid = Binder.getCallingUid();
+                final int appId = UserHandle.getAppId(input);
+                String[] packagesForUid = null;
+                for (int i = 0, size = users.length; packagesForUid == null && i < size; i++) {
+                    packagesForUid = getPackagesForUid.apply(callingUid, users[i], appId,
+                            false /*isCallerInstantApp*/);
+                }
                 if (packagesForUid == null) {
-                    cachedValue = "[unknown app id " + input + "]";
+                    cachedValue = "[app id " + input + " not installed]";
                 } else {
                     cachedValue = packagesForUid.length == 1 ? packagesForUid[0]
                             : "[" + TextUtils.join(",", packagesForUid) + "]";
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d2cbcf0..8d6c145 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4619,7 +4619,7 @@
                     Integer filteringAppId = setting == null ? null : setting.appId;
                     mAppsFilter.dumpQueries(
                             pw, filteringAppId, dumpState, mUserManager.getUserIds(),
-                            this::getPackagesForUid);
+                            this::getPackagesForUidInternalBody);
                     break;
                 }
 
@@ -21627,7 +21627,8 @@
                     null /*disabledComponents*/,
                     PackageManager.INSTALL_REASON_UNKNOWN,
                     PackageManager.UNINSTALL_REASON_UNKNOWN,
-                    null /*harmfulAppWarning*/);
+                    null /*harmfulAppWarning*/,
+                    null /*splashScreenTheme*/);
         }
         mSettings.writeKernelMappingLPr(ps);
     }
@@ -27701,6 +27702,23 @@
         return mSettings.getPackageLPr(packageName).getMimeGroup(mimeGroup);
     }
 
+    @Override
+    public void setSplashScreenTheme(@NonNull String packageName, @Nullable String themeId,
+            int userId) {
+        int callingUid = Binder.getCallingUid();
+        PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid, userId);
+        if (packageSetting != null) {
+            packageSetting.setSplashScreenTheme(userId, themeId);
+        }
+    }
+
+    @Override
+    public String getSplashScreenTheme(@NonNull String packageName, int userId) {
+        int callingUid = Binder.getCallingUid();
+        PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid, userId);
+        return packageSetting != null ? packageSetting.getSplashScreenTheme(userId) : null;
+    }
+
     /**
      * Temporary method that wraps mSettings.writeLPr() and calls mPermissionManager's
      * writeLegacyPermissionsTEMP() beforehand.
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 19b56b7..731d41c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -493,7 +493,8 @@
             ArrayMap<String, PackageUserState.SuspendParams> suspendParams, boolean instantApp,
             boolean virtualPreload, String lastDisableAppCaller,
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
-            int installReason, int uninstallReason, String harmfulAppWarning) {
+            int installReason, int uninstallReason, String harmfulAppWarning,
+            String splashScreenTheme) {
         PackageUserState state = modifyUserState(userId);
         state.ceDataInode = ceDataInode;
         state.enabled = enabled;
@@ -512,6 +513,7 @@
         state.instantApp = instantApp;
         state.virtualPreload = virtualPreload;
         state.harmfulAppWarning = harmfulAppWarning;
+        state.splashScreenTheme = splashScreenTheme;
         onChanged();
     }
 
@@ -522,7 +524,8 @@
                 otherState.instantApp,
                 otherState.virtualPreload, otherState.lastDisableAppCaller,
                 otherState.enabledComponents, otherState.disabledComponents,
-                otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning);
+                otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning,
+                otherState.splashScreenTheme);
     }
 
     ArraySet<String> getEnabledComponents(int userId) {
@@ -723,6 +726,26 @@
     }
 
     /**
+     * @param userId    the specified user to modify the theme for
+     * @param themeName the theme name to persist
+     * @see android.window.SplashScreen#setSplashScreenTheme(int)
+     */
+    public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
+        modifyUserState(userId).splashScreenTheme = themeName;
+    }
+
+    /**
+     * @param userId the specified user to get the theme setting from
+     * @return the theme name previously persisted for the user or null
+     * if no splashscreen theme is persisted.
+     * @see android.window.SplashScreen#setSplashScreenTheme(int)
+     */
+    @Nullable
+    public String getSplashScreenTheme(@UserIdInt int userId) {
+        return readUserState(userId).splashScreenTheme;
+    }
+
+    /**
      * @return True if package is still being loaded, false if the package is fully loaded.
      */
     public boolean isPackageLoading() {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e409019..b6d4a5b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -340,6 +340,7 @@
     private static final String ATTR_INSTANT_APP = "instant-app";
     private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
     private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
+    private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
 
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_FINGERPRINT = "fingerprint";
@@ -939,7 +940,9 @@
                                 null /*disabledComponents*/,
                                 PackageManager.INSTALL_REASON_UNKNOWN,
                                 PackageManager.UNINSTALL_REASON_UNKNOWN,
-                                null /*harmfulAppWarning*/);
+                                null, /*harmfulAppWarning*/
+                                null /*splashscreenTheme*/
+                        );
                     }
                 }
             }
@@ -1578,7 +1581,8 @@
                                 null /*disabledComponents*/,
                                 PackageManager.INSTALL_REASON_UNKNOWN,
                                 PackageManager.UNINSTALL_REASON_UNKNOWN,
-                                null /*harmfulAppWarning*/);
+                                null /*harmfulAppWarning*/,
+                                null /* splashScreenTheme*/);
                     }
                     return;
                 }
@@ -1666,6 +1670,8 @@
                             PackageManager.INSTALL_REASON_UNKNOWN);
                     final int uninstallReason = parser.getAttributeInt(null, ATTR_UNINSTALL_REASON,
                             PackageManager.UNINSTALL_REASON_UNKNOWN);
+                    final String splashScreenTheme = parser.getAttributeValue(null,
+                            ATTR_SPLASH_SCREEN_THEME);
 
                     ArraySet<String> enabledComponents = null;
                     ArraySet<String> disabledComponents = null;
@@ -1738,7 +1744,8 @@
                     ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
                             hidden, distractionFlags, suspended, suspendParamsMap,
                             instantApp, virtualPreload, enabledCaller, enabledComponents,
-                            disabledComponents, installReason, uninstallReason, harmfulAppWarning);
+                            disabledComponents, installReason, uninstallReason, harmfulAppWarning,
+                            splashScreenTheme);
 
                     mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
                 } else if (tagName.equals("preferred-activities")) {
@@ -1995,6 +2002,10 @@
                     serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
                             ustate.harmfulAppWarning);
                 }
+                if (ustate.splashScreenTheme != null) {
+                    serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
+                            ustate.splashScreenTheme);
+                }
                 if (ustate.suspended) {
                     for (int i = 0; i < ustate.suspendParams.size(); i++) {
                         final String suspendingPackage = ustate.suspendParams.keyAt(i);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 2d1178a..0147790 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -70,7 +70,9 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.AttributionSource;
+import android.content.AttributionSourceState;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
@@ -104,6 +106,7 @@
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.IPermissionChecker;
 import android.permission.IPermissionManager;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
@@ -164,6 +167,7 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -451,6 +455,7 @@
         if (permissionService == null) {
             permissionService = new PermissionManagerService(context, availableFeatures);
             ServiceManager.addService("permissionmgr", permissionService);
+            ServiceManager.addService("permission_checker", new PermissionCheckerService(context));
         }
         return LocalServices.getService(PermissionManagerServiceInternal.class);
     }
@@ -5414,4 +5419,419 @@
             }
         }
     }
+
+    /**
+     * TODO: We need to consolidate these APIs either on PermissionManager or an extension
+     * object or a separate PermissionChecker service in context. The impartant part is to
+     * keep a single impl that is exposed to Java and native. We are not sure about the
+     * API shape so let is soak a bit.
+     */
+    private static final class PermissionCheckerService extends IPermissionChecker.Stub {
+        // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
+        private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
+                = new ConcurrentHashMap<>();
+
+        private final @NonNull Context mContext;
+        private final @NonNull AppOpsManager mAppOpsManager;
+
+        PermissionCheckerService(@NonNull Context context) {
+            mContext = context;
+            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        }
+
+        @Override
+        @PermissionChecker.PermissionResult
+        public int checkPermission(@NonNull String permission,
+                @NonNull AttributionSourceState attributionSourceState, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+            Objects.requireNonNull(permission);
+            Objects.requireNonNull(attributionSourceState);
+            final AttributionSource attributionSource = new AttributionSource(
+                    attributionSourceState);
+            final int result = checkPermission(mContext, permission, attributionSource, message,
+                    forDataDelivery, startDataDelivery, fromDatasource);
+            // Finish any started op if some step in the attribution chain failed.
+            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+                finishDataDelivery(AppOpsManager.permissionToOp(permission),
+                        attributionSource.asState());
+            }
+            return result;
+        }
+
+        @Override
+        public void finishDataDelivery(@NonNull String op,
+                @NonNull AttributionSourceState attributionSourceState) {
+            if (op == null || attributionSourceState.packageName == null) {
+                return;
+            }
+            mAppOpsManager.finishProxyOp(op, new AttributionSource(attributionSourceState));
+            if (attributionSourceState.next != null) {
+                finishDataDelivery(op, attributionSourceState.next[0]);
+            }
+        }
+
+        @Override
+        @PermissionChecker.PermissionResult
+        public int checkOp(int op, AttributionSourceState attributionSource,
+                String message, boolean forDataDelivery, boolean startDataDelivery) {
+            int result = checkOp(mContext, op, new AttributionSource(attributionSource), message,
+                    forDataDelivery, startDataDelivery);
+            if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) {
+                // Finish any started op if some step in the attribution chain failed.
+                finishDataDelivery(AppOpsManager.opToName(op), attributionSource);
+            }
+            return  result;
+        }
+
+        @PermissionChecker.PermissionResult
+        private static int checkPermission(@NonNull Context context, @NonNull String permission,
+                @NonNull AttributionSource attributionSource, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+            PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
+
+            if (permissionInfo == null) {
+                try {
+                    permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+                    if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+                        // Double addition due to concurrency is fine - the backing
+                        // store is concurrent.
+                        sPlatformPermissions.put(permission, permissionInfo);
+                    }
+                } catch (PackageManager.NameNotFoundException ignored) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+            }
+
+            if (permissionInfo.isAppOp()) {
+                return checkAppOpPermission(context, permission, attributionSource, message,
+                        forDataDelivery, fromDatasource);
+            }
+            if (permissionInfo.isRuntime()) {
+                return checkRuntimePermission(context, permission, attributionSource, message,
+                        forDataDelivery, startDataDelivery, fromDatasource);
+            }
+
+            if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
+                    attributionSource.getRenouncedPermissions())) {
+                return PermissionChecker.PERMISSION_HARD_DENIED;
+            }
+
+            if (attributionSource.getNext() != null) {
+                return checkPermission(context, permission,
+                        attributionSource.getNext(), message, forDataDelivery,
+                        startDataDelivery, /*fromDatasource*/ false);
+            }
+
+            return PermissionChecker.PERMISSION_GRANTED;
+        }
+
+        @PermissionChecker.PermissionResult
+        private static int checkAppOpPermission(@NonNull Context context,
+                @NonNull String permission, @NonNull AttributionSource attributionSource,
+                @Nullable String message, boolean forDataDelivery, boolean fromDatasource) {
+            final int op = AppOpsManager.permissionToOpCode(permission);
+            if (op < 0) {
+                Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!");
+                return PermissionChecker.PERMISSION_HARD_DENIED;
+            }
+
+            AttributionSource current = attributionSource;
+            AttributionSource next = null;
+
+            while (true) {
+                final boolean skipCurrentChecks = (fromDatasource || next != null);
+
+                next = current.getNext();
+
+                // If the call is from a datasource we need to vet only the chain before it. This
+                // way we can avoid the datasource creating an attribution context for every call.
+                if (!(fromDatasource && current == attributionSource)
+                        && next != null && !current.isTrusted(context)) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                // The access is for oneself if this is the single receiver of data
+                // after the data source or if this is the single attribution source
+                // in the chain if not from a datasource.
+                final boolean singleReceiverFromDatasource = (fromDatasource
+                        && current == attributionSource && next != null && next.getNext() == null);
+                final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+                final int opMode = performOpTransaction(context, op, current, message,
+                        forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
+                        selfAccess, singleReceiverFromDatasource);
+
+                switch (opMode) {
+                    case AppOpsManager.MODE_IGNORED:
+                    case AppOpsManager.MODE_ERRORED: {
+                        return PermissionChecker.PERMISSION_HARD_DENIED;
+                    }
+                    case AppOpsManager.MODE_DEFAULT: {
+                        if (!skipCurrentChecks && !checkPermission(context, permission,
+                                attributionSource.getUid(), attributionSource
+                                        .getRenouncedPermissions())) {
+                            return PermissionChecker.PERMISSION_HARD_DENIED;
+                        }
+                        if (next != null && !checkPermission(context, permission,
+                                next.getUid(), next.getRenouncedPermissions())) {
+                            return PermissionChecker.PERMISSION_HARD_DENIED;
+                        }
+                    }
+                }
+
+                if (next == null || next.getNext() == null) {
+                    return PermissionChecker.PERMISSION_GRANTED;
+                }
+
+                current = next;
+            }
+        }
+
+        private static int checkRuntimePermission(@NonNull Context context,
+                @NonNull String permission, @NonNull AttributionSource attributionSource,
+                @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
+                boolean fromDatasource) {
+            // Now let's check the identity chain...
+            final int op = AppOpsManager.permissionToOpCode(permission);
+
+            AttributionSource current = attributionSource;
+            AttributionSource next = null;
+
+            while (true) {
+                final boolean skipCurrentChecks = (fromDatasource || next != null);
+                next = current.getNext();
+
+                // If the call is from a datasource we need to vet only the chain before it. This
+                // way we can avoid the datasource creating an attribution context for every call.
+                if (!(fromDatasource && current == attributionSource)
+                        && next != null && !current.isTrusted(context)) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                // If we already checked the permission for this one, skip the work
+                if (!skipCurrentChecks && !checkPermission(context, permission,
+                        current.getUid(), current.getRenouncedPermissions())) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                if (next != null && !checkPermission(context, permission,
+                        next.getUid(), next.getRenouncedPermissions())) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                if (op < 0) {
+                    // Bg location is one-off runtime modifier permission and has no app op
+                    if (sPlatformPermissions.contains(permission)
+                            && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+                        Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
+                                + " with no app op defined!");
+                    }
+                    if (next == null) {
+                        return PermissionChecker.PERMISSION_GRANTED;
+                    }
+                    current = next;
+                    continue;
+                }
+
+                // The access is for oneself if this is the single receiver of data
+                // after the data source or if this is the single attribution source
+                // in the chain if not from a datasource.
+                final boolean singleReceiverFromDatasource = (fromDatasource
+                        && current == attributionSource && next != null && next.getNext() == null);
+                final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+                final int opMode = performOpTransaction(context, op, current, message,
+                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+                        singleReceiverFromDatasource);
+
+                switch (opMode) {
+                    case AppOpsManager.MODE_ERRORED: {
+                        return PermissionChecker.PERMISSION_HARD_DENIED;
+                    }
+                    case AppOpsManager.MODE_IGNORED: {
+                        return PermissionChecker.PERMISSION_SOFT_DENIED;
+                    }
+                }
+
+                if (next == null || next.getNext() == null) {
+                    return PermissionChecker.PERMISSION_GRANTED;
+                }
+
+                current = next;
+            }
+        }
+
+        private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
+                int uid, @NonNull Set<String> renouncedPermissions) {
+            final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
+                    uid) == PackageManager.PERMISSION_GRANTED;
+            if (permissionGranted && renouncedPermissions.contains(permission)
+                    && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
+                    /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+            return permissionGranted;
+        }
+
+        private static int checkOp(@NonNull Context context, @NonNull int op,
+                @NonNull AttributionSource attributionSource, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery) {
+            if (op < 0 || attributionSource.getPackageName() == null) {
+                return PermissionChecker.PERMISSION_HARD_DENIED;
+            }
+
+            AttributionSource current = attributionSource;
+            AttributionSource next = null;
+
+            while (true) {
+                final boolean skipCurrentChecks = (next != null);
+                next = current.getNext();
+
+                // If the call is from a datasource we need to vet only the chain before it. This
+                // way we can avoid the datasource creating an attribution context for every call.
+                if (next != null && !current.isTrusted(context)) {
+                    return PermissionChecker.PERMISSION_HARD_DENIED;
+                }
+
+                // The access is for oneself if this is the single attribution source in the chain.
+                final boolean selfAccess = (next == null);
+
+                final int opMode = performOpTransaction(context, op, current, message,
+                        forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+                        /*fromDatasource*/ false);
+
+                switch (opMode) {
+                    case AppOpsManager.MODE_ERRORED: {
+                        return PermissionChecker.PERMISSION_HARD_DENIED;
+                    }
+                    case AppOpsManager.MODE_IGNORED: {
+                        return PermissionChecker.PERMISSION_SOFT_DENIED;
+                    }
+                }
+
+                if (next == null || next.getNext() == null) {
+                    return PermissionChecker.PERMISSION_GRANTED;
+                }
+
+                current = next;
+            }
+        }
+
+        private static int performOpTransaction(@NonNull Context context, int op,
+                @NonNull AttributionSource attributionSource, @Nullable String message,
+                boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
+                boolean selfAccess, boolean singleReceiverFromDatasource) {
+            // We cannot perform app ops transactions without a package name. In all relevant
+            // places we pass the package name but just in case there is a bug somewhere we
+            // do a best effort to resolve the package from the UID (pick first without a loss
+            // of generality - they are in the same security sandbox).
+            final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+            final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+                    ? attributionSource : attributionSource.getNext();
+            if (!forDataDelivery) {
+                final String resolvedAccessorPackageName = resolvePackageName(context,
+                        accessorSource);
+                if (resolvedAccessorPackageName == null) {
+                    return AppOpsManager.MODE_ERRORED;
+                }
+                final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
+                        accessorSource.getUid(), resolvedAccessorPackageName);
+                final AttributionSource next = accessorSource.getNext();
+                if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
+                    final String resolvedNextPackageName = resolvePackageName(context, next);
+                    if (resolvedNextPackageName == null) {
+                        return AppOpsManager.MODE_ERRORED;
+                    }
+                    return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
+                            resolvedNextPackageName);
+                }
+                return opMode;
+            } else if (startDataDelivery) {
+                final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+                        context, accessorSource);
+                if (resolvedAttributionSource.getPackageName() == null) {
+                    return AppOpsManager.MODE_ERRORED;
+                }
+                if (selfAccess) {
+                    // If the datasource is not in a trusted platform component then in would not
+                    // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+                    // an app is exposing runtime permission protected data but cannot blame others
+                    // in a trusted way which would not properly show in permission usage UIs.
+                    // As a fallback we note a proxy op that blames the app and the datasource.
+                    try {
+                        return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
+                                resolvedAttributionSource.getPackageName(),
+                                /*startIfModeDefault*/ false,
+                                resolvedAttributionSource.getAttributionTag(),
+                                message);
+                    } catch (SecurityException e) {
+                        Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+                                + " platform defined runtime permission "
+                                + AppOpsManager.opToPermission(op) + " while not having "
+                                + Manifest.permission.UPDATE_APP_OPS_STATS);
+                        return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
+                                skipProxyOperation);
+                    }
+                } else {
+                    return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
+                            skipProxyOperation);
+                }
+            } else {
+                final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+                        context, accessorSource);
+                if (resolvedAttributionSource.getPackageName() == null) {
+                    return AppOpsManager.MODE_ERRORED;
+                }
+                if (selfAccess) {
+                    // If the datasource is not in a trusted platform component then in would not
+                    // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+                    // an app is exposing runtime permission protected data but cannot blame others
+                    // in a trusted way which would not properly show in permission usage UIs.
+                    // As a fallback we note a proxy op that blames the app and the datasource.
+                    try {
+                        return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
+                                resolvedAttributionSource.getPackageName(),
+                                resolvedAttributionSource.getAttributionTag(),
+                                message);
+                    } catch (SecurityException e) {
+                        Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+                                + " platform defined runtime permission "
+                                + AppOpsManager.opToPermission(op) + " while not having "
+                                + Manifest.permission.UPDATE_APP_OPS_STATS);
+                        return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
+                                skipProxyOperation);
+                    }
+                } else {
+                    return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
+                            skipProxyOperation);
+                }
+            }
+        }
+
+        private static @Nullable String resolvePackageName(@NonNull Context context,
+                @NonNull AttributionSource attributionSource) {
+            if (attributionSource.getPackageName() != null) {
+                return attributionSource.getPackageName();
+            }
+            final String[] packageNames = context.getPackageManager().getPackagesForUid(
+                    attributionSource.getUid());
+            if (packageNames != null) {
+                // This is best effort if the caller doesn't pass a package. The security
+                // sandbox is UID, therefore we pick an arbitrary package.
+                return packageNames[0];
+            }
+            // Last resort to handle special UIDs like root, etc.
+            return AppOpsManager.resolvePackageName(attributionSource.getUid(),
+                    attributionSource.getPackageName());
+        }
+
+        private static @NonNull AttributionSource resolveAttributionSource(
+                @NonNull Context context, @NonNull AttributionSource attributionSource) {
+            if (attributionSource.getPackageName() != null) {
+                return attributionSource;
+            }
+            return attributionSource.withPackageName(resolvePackageName(context,
+                    attributionSource));
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index a8a6a72..a5ba82f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.verify.domain;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.Intent;
@@ -33,6 +34,8 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.util.List;
+import java.util.Objects;
+import java.util.function.BiFunction;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -44,6 +47,12 @@
 
     private static final int MAX_DOMAINS_BYTE_SIZE = 1024 * 1024;
 
+    private static final BiFunction<ArraySet<String>, String, Boolean> ARRAY_SET_COLLECTOR =
+            (set, domain) -> {
+                set.add(domain);
+                return null;
+            };
+
     @NonNull
     private final PlatformCompat mPlatformCompat;
 
@@ -105,27 +114,62 @@
         return collectDomains(pkg, true /* checkAutoVerify */, false /* valid */);
     }
 
+    public boolean containsWebDomain(@NonNull AndroidPackage pkg, @NonNull String targetDomain) {
+        return collectDomains(pkg, false /* checkAutoVerify */, true /* valid */, null,
+                (BiFunction<Void, String, Boolean>) (unused, domain) -> {
+                    if (Objects.equals(targetDomain, domain)) {
+                        return true;
+                    }
+                    return null;
+                }) != null;
+    }
+
+    public boolean containsAutoVerifyDomain(@NonNull AndroidPackage pkg,
+            @NonNull String targetDomain) {
+        return collectDomains(pkg, true /* checkAutoVerify */, true /* valid */, null,
+                (BiFunction<Void, String, Boolean>) (unused, domain) -> {
+                    if (Objects.equals(targetDomain, domain)) {
+                        return true;
+                    }
+                    return null;
+                }) != null;
+    }
+
     @NonNull
     private ArraySet<String> collectDomains(@NonNull AndroidPackage pkg,
             boolean checkAutoVerify, boolean valid) {
+        ArraySet<String> domains = new ArraySet<>();
+        collectDomains(pkg, checkAutoVerify, valid, domains, ARRAY_SET_COLLECTOR);
+        return domains;
+    }
+
+    @NonNull
+    private <InitialValue, ReturnValue> ReturnValue collectDomains(@NonNull AndroidPackage pkg,
+            boolean checkAutoVerify, boolean valid, @Nullable InitialValue initialValue,
+            @NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
         boolean restrictDomains =
                 DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS);
 
         if (restrictDomains) {
-            return collectDomainsInternal(pkg, checkAutoVerify, valid);
+            return collectDomainsInternal(pkg, checkAutoVerify, valid, initialValue,
+                    domainCollector);
         } else {
-            return collectDomainsLegacy(pkg, checkAutoVerify, valid);
+            return collectDomainsLegacy(pkg, checkAutoVerify, valid, initialValue, domainCollector);
         }
     }
 
     /**
      * @see #RESTRICT_DOMAINS
      */
-    private ArraySet<String> collectDomainsLegacy(@NonNull AndroidPackage pkg,
-            boolean checkAutoVerify, boolean valid) {
+    @Nullable
+    private <InitialValue, ReturnValue> ReturnValue collectDomainsLegacy(
+            @NonNull AndroidPackage pkg, boolean checkAutoVerify, boolean valid,
+            @Nullable InitialValue initialValue,
+            @NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
         if (!checkAutoVerify) {
             // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2
-            return collectDomainsInternal(pkg, false /* checkAutoVerify */, true /* valid */);
+            return collectDomainsInternal(pkg, false /* checkAutoVerify */, true /* valid */,
+                    initialValue, domainCollector);
         }
 
         List<ParsedActivity> activities = pkg.getActivities();
@@ -148,11 +192,10 @@
             }
 
             if (!needsAutoVerify) {
-                return new ArraySet<>();
+                return null;
             }
         }
 
-        ArraySet<String> domains = new ArraySet<>();
         int totalSize = 0;
         boolean underMaxSize = true;
         for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize;
@@ -169,22 +212,30 @@
                         if (isValidHost(host) == valid) {
                             totalSize += byteSizeOf(host);
                             underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
-                            domains.add(host);
+                            ReturnValue returnValue = domainCollector.apply(initialValue, host);
+                            if (returnValue != null) {
+                                return returnValue;
+                            }
                         }
                     }
                 }
             }
         }
 
-        return domains;
+        return null;
     }
 
     /**
      * @see #RESTRICT_DOMAINS
+     * @param domainCollector Function to call with initialValue and a valid host. Should return
+     *                        a non-null value if the function should return immediately
+     *                        after the currently processed host.
      */
-    private ArraySet<String> collectDomainsInternal(@NonNull AndroidPackage pkg,
-            boolean checkAutoVerify, boolean valid) {
-        ArraySet<String> domains = new ArraySet<>();
+    @Nullable
+    private <InitialValue, ReturnValue> ReturnValue collectDomainsInternal(
+            @NonNull AndroidPackage pkg, boolean checkAutoVerify, boolean valid,
+            @Nullable InitialValue initialValue,
+            @NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
         int totalSize = 0;
         boolean underMaxSize = true;
 
@@ -226,13 +277,16 @@
                     if (isValidHost(host) == valid) {
                         totalSize += byteSizeOf(host);
                         underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
-                        domains.add(host);
+                        ReturnValue returnValue = domainCollector.apply(initialValue, host);
+                        if (returnValue != null) {
+                            return returnValue;
+                        }
                     }
                 }
             }
         }
 
-        return domains;
+        return null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index 1d04478..adf8f0d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -37,6 +37,7 @@
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.Function;
 
 @SuppressWarnings("PointlessBooleanExpression")
@@ -100,6 +101,70 @@
         }
     }
 
+    /**
+     * @param userIdToApprovalLevelToOwners Mapping of user ID to approval level to domain owners.
+     */
+    public void printOwners(@NonNull IndentingPrintWriter writer, @NonNull String domain,
+            SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners) {
+        writer.println(domain + ":");
+        writer.increaseIndent();
+
+        if (userIdToApprovalLevelToOwners.size() == 0) {
+            writer.println("none");
+            writer.decreaseIndent();
+            return;
+        }
+
+        int usersSize = userIdToApprovalLevelToOwners.size();
+        for (int userIndex = 0; userIndex < usersSize; userIndex++) {
+            int userId = userIdToApprovalLevelToOwners.keyAt(userIndex);
+            SparseArray<List<String>> approvalLevelToOwners =
+                    userIdToApprovalLevelToOwners.valueAt(userIndex);
+
+            if (approvalLevelToOwners.size() == 0) {
+                continue;
+            }
+
+            boolean printedUserHeader = false;
+            int approvalsSize = approvalLevelToOwners.size();
+            for (int approvalIndex = 0; approvalIndex < approvalsSize; approvalIndex++) {
+                int approvalLevel = approvalLevelToOwners.keyAt(approvalIndex);
+                if (approvalLevel < DomainVerificationManagerInternal.APPROVAL_LEVEL_UNVERIFIED) {
+                    continue;
+                }
+
+                if (!printedUserHeader) {
+                    writer.println("User " + userId + ":");
+                    writer.increaseIndent();
+                    printedUserHeader = true;
+                }
+
+                String approvalString =
+                        DomainVerificationManagerInternal.approvalLevelToDebugString(approvalLevel);
+                List<String> owners = approvalLevelToOwners.valueAt(approvalIndex);
+                writer.println(approvalString + "[" + approvalLevel + "]" + ":");
+                writer.increaseIndent();
+
+                if (owners.size() == 0) {
+                    writer.println("none");
+                    writer.decreaseIndent();
+                    continue;
+                }
+
+                int ownersSize = owners.size();
+                for (int ownersIndex = 0; ownersIndex < ownersSize; ownersIndex++) {
+                    writer.println(owners.get(ownersIndex));
+                }
+                writer.decreaseIndent();
+            }
+
+            if (printedUserHeader) {
+                writer.decreaseIndent();
+            }
+        }
+        writer.decreaseIndent();
+    }
+
     boolean printState(@NonNull IndentingPrintWriter writer,
             @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
             @NonNull ArrayMap<String, Integer> reusedMap, boolean wasHeaderPrinted) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 0f99e19..5aed367 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -57,6 +57,28 @@
     UUID DISABLED_ID = new UUID(0, 0);
 
     /**
+     * The app was not installed for the user.
+     */
+    int APPROVAL_LEVEL_NOT_INSTALLED = -4;
+
+    /**
+     * The app was not enabled for the user.
+     */
+    int APPROVAL_LEVEL_DISABLED = -3;
+
+    /**
+     * The app has not declared this domain in a valid web intent-filter in their manifest, and so
+     * would never be able to be approved for this domain.
+     */
+    int APPROVAL_LEVEL_UNDECLARED = -2;
+
+    /**
+     * The app has declared this domain as a valid autoVerify domain, but it failed or has not
+     * succeeded verification.
+     */
+    int APPROVAL_LEVEL_UNVERIFIED = -1;
+
+    /**
      * The app has not been approved for this domain and should never be able to open it through
      * an implicit web intent.
      */
@@ -117,10 +139,14 @@
      * by approval priority. A higher numerical value means the package should override all lower
      * values. This means that comparison using less/greater than IS valid.
      *
-     * Negative values are possible, although not implemented, reserved if explicit disable of a
-     * package for a domain needs to be tracked.
+     * Negative values are possible, used for tracking specific reasons for why an app doesn't have
+     * approval.
      */
     @IntDef({
+            APPROVAL_LEVEL_NOT_INSTALLED,
+            APPROVAL_LEVEL_DISABLED,
+            APPROVAL_LEVEL_UNDECLARED,
+            APPROVAL_LEVEL_UNVERIFIED,
             APPROVAL_LEVEL_NONE,
             APPROVAL_LEVEL_LEGACY_ASK,
             APPROVAL_LEVEL_LEGACY_ALWAYS,
@@ -131,6 +157,33 @@
     @interface ApprovalLevel {
     }
 
+    static String approvalLevelToDebugString(@ApprovalLevel int level) {
+        switch (level) {
+            case APPROVAL_LEVEL_NOT_INSTALLED:
+                return "NOT_INSTALLED";
+            case APPROVAL_LEVEL_DISABLED:
+                return "DISABLED";
+            case APPROVAL_LEVEL_UNDECLARED:
+                return "UNDECLARED";
+            case APPROVAL_LEVEL_UNVERIFIED:
+                return "UNVERIFIED";
+            case APPROVAL_LEVEL_NONE:
+                return "NONE";
+            case APPROVAL_LEVEL_LEGACY_ASK:
+                return "LEGACY_ASK";
+            case APPROVAL_LEVEL_LEGACY_ALWAYS:
+                return "LEGACY_ALWAYS";
+            case APPROVAL_LEVEL_SELECTION:
+                return "USER_SELECTION";
+            case APPROVAL_LEVEL_VERIFIED:
+                return "VERIFIED";
+            case APPROVAL_LEVEL_INSTANT_APP:
+                return "INSTANT_APP";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
     /** @see DomainVerificationManager#getDomainVerificationInfo(String) */
     @Nullable
     @RequiresPermission(anyOf = {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index a3e1a9c..3a4b849 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -741,62 +741,21 @@
         });
     }
 
+    @NonNull
     public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
         Objects.requireNonNull(domain);
         mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
                 userId);
 
-        SparseArray<List<String>> levelToPackages = new SparseArray<>();
         return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
-            // First, collect the raw approval level values
-            synchronized (mLock) {
-                final int size = mAttachedPkgStates.size();
-                for (int index = 0; index < size; index++) {
-                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
-                    String packageName = pkgState.getPackageName();
-                    PackageSetting pkgSetting = pkgSettings.apply(packageName);
-                    if (pkgSetting == null) {
-                        continue;
-                    }
-
-                    int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
-                    if (level <= APPROVAL_LEVEL_NONE) {
-                        continue;
-                    }
-                    List<String> list = levelToPackages.get(level);
-                    if (list == null) {
-                        list = new ArrayList<>();
-                        levelToPackages.put(level, list);
-                    }
-                    list.add(packageName);
-                }
-            }
-
-            final int size = levelToPackages.size();
-            if (size == 0) {
+            SparseArray<List<String>> levelToPackages = getOwnersForDomainInternal(domain, false,
+                    userId, pkgSettings);
+            if (levelToPackages.size() == 0) {
                 return emptyList();
             }
 
-            // Then sort them ascending by first installed time, with package name as tie breaker
-            for (int index = 0; index < size; index++) {
-                levelToPackages.valueAt(index).sort((first, second) -> {
-                    PackageSetting firstPkgSetting = pkgSettings.apply(first);
-                    PackageSetting secondPkgSetting = pkgSettings.apply(second);
-
-                    long firstInstallTime =
-                            firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
-                    long secondInstallTime =
-                            secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
-
-                    if (firstInstallTime != secondInstallTime) {
-                        return (int) (firstInstallTime - secondInstallTime);
-                    }
-
-                    return first.compareToIgnoreCase(second);
-                });
-            }
-
             List<DomainOwner> owners = new ArrayList<>();
+            int size = levelToPackages.size();
             for (int index = 0; index < size; index++) {
                 int level = levelToPackages.keyAt(index);
                 boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
@@ -811,6 +770,69 @@
         });
     }
 
+    /**
+     * @param includeNegative See {@link #approvalLevelForDomain(PackageSetting, String, boolean,
+     *                        int, Object)}.
+     * @return Mapping of approval level to packages; packages are sorted by firstInstallTime. Null
+     * if no owners were found.
+     */
+    @NonNull
+    private SparseArray<List<String>> getOwnersForDomainInternal(@NonNull String domain,
+            boolean includeNegative, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        SparseArray<List<String>> levelToPackages = new SparseArray<>();
+        // First, collect the raw approval level values
+        synchronized (mLock) {
+            final int size = mAttachedPkgStates.size();
+            for (int index = 0; index < size; index++) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                String packageName = pkgState.getPackageName();
+                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+                if (pkgSetting == null) {
+                    continue;
+                }
+
+                int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
+                        domain);
+                if (!includeNegative && level <= APPROVAL_LEVEL_NONE) {
+                    continue;
+                }
+                List<String> list = levelToPackages.get(level);
+                if (list == null) {
+                    list = new ArrayList<>();
+                    levelToPackages.put(level, list);
+                }
+                list.add(packageName);
+            }
+        }
+
+        final int size = levelToPackages.size();
+        if (size == 0) {
+            return levelToPackages;
+        }
+
+        // Then sort them ascending by first installed time, with package name as tie breaker
+        for (int index = 0; index < size; index++) {
+            levelToPackages.valueAt(index).sort((first, second) -> {
+                PackageSetting firstPkgSetting = pkgSettingFunction.apply(first);
+                PackageSetting secondPkgSetting = pkgSettingFunction.apply(second);
+
+                long firstInstallTime =
+                        firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
+                long secondInstallTime =
+                        secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
+
+                if (firstInstallTime != secondInstallTime) {
+                    return (int) (firstInstallTime - secondInstallTime);
+                }
+
+                return first.compareToIgnoreCase(second);
+            });
+        }
+
+        return levelToPackages;
+    }
+
     @NonNull
     @Override
     public UUID generateNewId() {
@@ -1153,6 +1175,88 @@
         }
     }
 
+    @Override
+    public void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
+            @Nullable String packageName, @Nullable @UserIdInt Integer userId)
+            throws NameNotFoundException {
+        mConnection.withPackageSettingsThrowing(pkgSettings -> {
+            synchronized (mLock) {
+                if (packageName == null) {
+                    int size = mAttachedPkgStates.size();
+                    for (int index = 0; index < size; index++) {
+                        try {
+                            printOwnersForPackage(writer,
+                                    mAttachedPkgStates.valueAt(index).getPackageName(), userId,
+                                    pkgSettings);
+                        } catch (NameNotFoundException ignored) {
+                            // When iterating packages, if one doesn't exist somehow, ignore
+                        }
+                    }
+                } else {
+                    printOwnersForPackage(writer, packageName, userId, pkgSettings);
+                }
+            }
+        });
+    }
+
+    private void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
+            @NonNull String packageName, @Nullable @UserIdInt Integer userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction)
+            throws NameNotFoundException {
+        PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+        AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+        if (pkg == null) {
+            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+        }
+
+        ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
+        int size = domains.size();
+        if (size == 0) {
+            return;
+        }
+
+        writer.println(packageName + ":");
+        writer.increaseIndent();
+
+        for (int index = 0; index < size; index++) {
+            printOwnersForDomain(writer, domains.valueAt(index), userId, pkgSettingFunction);
+        }
+
+        writer.decreaseIndent();
+    }
+
+    @Override
+    public void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
+            @NonNull List<String> domains, @Nullable @UserIdInt Integer userId) {
+        mConnection.withPackageSettings(pkgSettings -> {
+            synchronized (mLock) {
+                int size = domains.size();
+                for (int index = 0; index < size; index++) {
+                    printOwnersForDomain(writer, domains.get(index), userId, pkgSettings);
+                }
+            }
+        });
+    }
+
+    private void printOwnersForDomain(@NonNull IndentingPrintWriter writer, @NonNull String domain,
+            @Nullable @UserIdInt Integer userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners =
+                new SparseArray<>();
+
+        if (userId == null || userId == UserHandle.USER_ALL) {
+            for (int aUserId : mConnection.getAllUserIds()) {
+                userIdToApprovalLevelToOwners.put(aUserId,
+                        getOwnersForDomainInternal(domain, true, aUserId, pkgSettingFunction));
+            }
+        } else {
+            userIdToApprovalLevelToOwners.put(userId,
+                    getOwnersForDomainInternal(domain, true, userId, pkgSettingFunction));
+        }
+
+        mDebug.printOwners(writer, domain, userIdToApprovalLevelToOwners);
+    }
+
     @NonNull
     @Override
     public DomainVerificationShell getShell() {
@@ -1427,7 +1531,7 @@
         // Find all approval levels
         int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId,
                 pkgSettingFunction);
-        if (highestApproval == APPROVAL_LEVEL_NONE) {
+        if (highestApproval <= APPROVAL_LEVEL_NONE) {
             return Pair.create(emptyList(), highestApproval);
         }
 
@@ -1484,7 +1588,7 @@
                 fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE);
                 continue;
             }
-            int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+            int approval = approvalLevelForDomain(pkgSetting, domain, false, userId, domain);
             highestApproval = Math.max(highestApproval, approval);
             fillInfoMapForSamePackage(inputMap, packageName, approval);
         }
@@ -1600,18 +1704,53 @@
             return APPROVAL_LEVEL_NONE;
         }
 
-        return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
+        return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), false, userId,
+                intent);
     }
 
     /**
-     * @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
-     *                    otherwise.
+     * @param includeNegative Whether to include negative values, which requires an expensive
+     *                          domain comparison operation.
+     * @param debugObject       Should be an {@link Intent} if checking for resolution or a
+     *                          {@link String} otherwise.
      */
     private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
-            @UserIdInt int userId, @NonNull Object debugObject) {
+            boolean includeNegative, @UserIdInt int userId, @NonNull Object debugObject) {
+        int approvalLevel = approvalLevelForDomainInternal(pkgSetting, host, includeNegative,
+                userId, debugObject);
+        if (includeNegative && approvalLevel == APPROVAL_LEVEL_NONE) {
+            PackageUserState pkgUserState = pkgSetting.readUserState(userId);
+            if (!pkgUserState.installed) {
+                return APPROVAL_LEVEL_NOT_INSTALLED;
+            }
+
+            AndroidPackage pkg = pkgSetting.getPkg();
+            if (pkg != null) {
+                if (!pkgUserState.isPackageEnabled(pkg)) {
+                    return APPROVAL_LEVEL_DISABLED;
+                } else if (mCollector.containsAutoVerifyDomain(pkgSetting.getPkg(), host)) {
+                    return APPROVAL_LEVEL_UNVERIFIED;
+                }
+            }
+        }
+
+        return approvalLevel;
+    }
+
+    private int approvalLevelForDomainInternal(@NonNull PackageSetting pkgSetting,
+            @NonNull String host, boolean includeNegative, @UserIdInt int userId,
+            @NonNull Object debugObject) {
         String packageName = pkgSetting.getName();
         final AndroidPackage pkg = pkgSetting.getPkg();
 
+        if (pkg != null && includeNegative && !mCollector.containsWebDomain(pkg, host)) {
+            if (DEBUG_APPROVAL) {
+                debugApproval(packageName, debugObject, userId, false,
+                        "domain not declared");
+            }
+            return APPROVAL_LEVEL_UNDECLARED;
+        }
+
         final PackageUserState pkgUserState = pkgSetting.readUserState(userId);
         if (pkgUserState == null) {
             if (DEBUG_APPROVAL) {
@@ -1749,6 +1888,7 @@
     private Pair<List<String>, Integer> getApprovedPackagesLocked(@NonNull String domain,
             @UserIdInt int userId, int minimumApproval,
             @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        boolean includeNegative = minimumApproval < APPROVAL_LEVEL_NONE;
         int highestApproval = minimumApproval;
         List<String> approvedPackages = emptyList();
 
@@ -1762,7 +1902,8 @@
                     continue;
                 }
 
-                int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+                int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
+                        domain);
                 if (level < minimumApproval) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index ea71b28..da2d162 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -110,6 +110,13 @@
         pw.println("        packages will be reset if no one package is specified.");
         pw.println("      <ALLOWED>: true to allow the package to open auto verified links, false");
         pw.println("        to disable");
+        pw.println("  get-app-link-owners [--user <USER_ID>] [--package <PACKAGE>] [<DOMAINS>]");
+        pw.println("    Print the owners for a specific domain for a given user in low to high");
+        pw.println("    priority order.");
+        pw.println("      --user <USER_ID>: the user to query for");
+        pw.println("      --package <PACKAGE>: optionally also print for all web domains declared");
+        pw.println("        by a package, or \"all\" to print all packages");
+        pw.println("      --<DOMAINS>: space separated list of domains to query for");
     }
 
     /**
@@ -132,6 +139,8 @@
                 return runSetAppLinksUserState(commandHandler);
             case "set-app-links-allowed":
                 return runSetAppLinksAllowed(commandHandler);
+            case "get-app-link-owners":
+                return runGetAppLinkOwners(commandHandler);
         }
 
         return null;
@@ -420,6 +429,67 @@
         return true;
     }
 
+    // pm get-app-link-owners [--user <USER_ID>] [--package <PACKAGE>] [<DOMAINS>]
+    private boolean runGetAppLinkOwners(@NonNull BasicShellCommandHandler commandHandler) {
+        String packageName = null;
+        Integer userId = null;
+        String option;
+        while ((option = commandHandler.getNextOption()) != null) {
+            switch (option) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+                    break;
+                case "--package":
+                    packageName = commandHandler.getNextArgRequired();
+                    if (TextUtils.isEmpty(packageName)) {
+                        commandHandler.getErrPrintWriter().println("Error: no package specified");
+                        return false;
+                    }
+                    break;
+                default:
+                    commandHandler.getErrPrintWriter().println(
+                            "Error: unexpected option: " + option);
+                    return false;
+            }
+        }
+
+        ArrayList<String> domains = getRemainingArgs(commandHandler);
+        if (domains.isEmpty() && TextUtils.isEmpty(packageName)) {
+            commandHandler.getErrPrintWriter()
+                    .println("Error: no package name or domain specified");
+            return false;
+        }
+
+        if (userId != null) {
+            userId = translateUserId(userId, "runSetAppLinksAllowed");
+        }
+
+        try (IndentingPrintWriter writer = new IndentingPrintWriter(
+                commandHandler.getOutPrintWriter(), /* singleIndent */ "  ", /* wrapLength */
+                120)) {
+            writer.increaseIndent();
+            if (packageName != null) {
+                if (packageName.equals("all")) {
+                    packageName = null;
+                }
+
+                try {
+                    mCallback.printOwnersForPackage(writer, packageName, userId);
+                } catch (NameNotFoundException e) {
+                    commandHandler.getErrPrintWriter()
+                            .println("Error: package not found: " + packageName);
+                    return false;
+                }
+            }
+            if (!domains.isEmpty()) {
+                mCallback.printOwnersForDomains(writer, domains, userId);
+            }
+            writer.decreaseIndent();
+            return true;
+        }
+    }
+
+    @NonNull
     private ArrayList<String> getRemainingArgs(@NonNull BasicShellCommandHandler commandHandler) {
         ArrayList<String> args = new ArrayList<>();
         String arg;
@@ -534,5 +604,18 @@
          */
         void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
                 @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
+
+        /**
+         * Print the owners for all domains in a given package.
+         */
+        void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
+                @Nullable String packageName, @Nullable @UserIdInt Integer userId)
+                throws NameNotFoundException;
+
+        /**
+         * Print the owners for the given domains.
+         */
+        void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
+                @NonNull List<String> domains, @Nullable @UserIdInt Integer userId);
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9acbdcc..c888e54 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2384,17 +2384,19 @@
     public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
         synchronized (mLock) {
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            data.connection.forEachDisplayConnector(
-                    displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
-                            } catch (RemoteException e) {
-                                e.printStackTrace();
+            if (data != null && data.connection != null) {
+                data.connection.forEachDisplayConnector(
+                        displayConnector -> {
+                            if (displayConnector.mEngine != null) {
+                                try {
+                                    displayConnector.mEngine.dispatchWallpaperCommand(
+                                            WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+                                } catch (RemoteException e) {
+                                    e.printStackTrace();
+                                }
                             }
-                        }
-                    });
+                        });
+            }
         }
     }
 
@@ -2404,17 +2406,20 @@
     public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
         synchronized (mLock) {
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
-            data.connection.forEachDisplayConnector(
-                    displayConnector -> {
-                        if (displayConnector.mEngine != null) {
-                            try {
-                                displayConnector.mEngine.dispatchWallpaperCommand(
-                                        WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1, extras);
-                            } catch (RemoteException e) {
-                                e.printStackTrace();
+            if (data != null && data.connection != null) {
+                data.connection.forEachDisplayConnector(
+                        displayConnector -> {
+                            if (displayConnector.mEngine != null) {
+                                try {
+                                    displayConnector.mEngine.dispatchWallpaperCommand(
+                                            WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
+                                            extras);
+                                } catch (RemoteException e) {
+                                    e.printStackTrace();
+                                }
                             }
-                        }
-                    });
+                        });
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f279221..3e8bc5d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3523,7 +3523,7 @@
         }
 
         // Reset the last saved PiP snap fraction on removal.
-        mDisplayContent.mPinnedTaskControllerLocked.onActivityHidden(mActivityComponent);
+        mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
         mWmService.mEmbeddedWindowController.onActivityRemoved(this);
         mRemovingFromDisplay = false;
     }
@@ -4882,7 +4882,7 @@
         ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppStopped: %s", this);
         mAppStopped = true;
         // Reset the last saved PiP snap fraction on app stop.
-        mDisplayContent.mPinnedTaskControllerLocked.onActivityHidden(mActivityComponent);
+        mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
         destroySurfaces();
         // Remove any starting window that was added for this app if they are still around.
         removeStartingWindow();
@@ -6405,8 +6405,8 @@
         }
         clearThumbnail();
         final Transaction transaction = getAnimatingContainer().getPendingTransaction();
-        mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
-                transaction, getAnimatingContainer(), thumbnailHeader);
+        mThumbnail = new WindowContainerThumbnail(transaction, getAnimatingContainer(),
+                thumbnailHeader);
         mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader));
     }
 
@@ -6435,8 +6435,7 @@
             return;
         }
         final Transaction transaction = getPendingTransaction();
-        mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
-                transaction, getTask(), thumbnail);
+        mThumbnail = new WindowContainerThumbnail(transaction, getTask(), thumbnail);
         final Animation animation =
                 getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
                         frame);
@@ -6605,6 +6604,7 @@
             return;
         }
 
+        mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform(task);
         // Perform rotation animation according to the rotation of this activity.
         startFreezingScreen(originalDisplayRotation);
         // This activity may relaunch or perform configuration change so once it has reported drawn,
@@ -6996,6 +6996,11 @@
             // fixed-orientation requests.
             return;
         }
+        if (newParentConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED) {
+            // PiP bounds have higher priority than the requested orientation. Otherwise the
+            // activity may be squeezed into a small piece.
+            return;
+        }
 
         final Rect resolvedBounds =
                 getResolvedOverrideConfiguration().windowConfiguration.getBounds();
@@ -7387,8 +7392,9 @@
             }
         }
 
+        final boolean wasInPictureInPicture = inPinnedWindowingMode();
         final DisplayContent display = mDisplayContent;
-        if (inPinnedWindowingMode() && attachedToProcess() && display != null) {
+        if (wasInPictureInPicture && attachedToProcess() && display != null) {
             // If the PIP activity is changing to fullscreen with display orientation change, the
             // fixed rotation will take effect that requires to send fixed rotation adjustments
             // before the process configuration (if the process is a configuration listener of the
@@ -7420,6 +7426,13 @@
             onMergedOverrideConfigurationChanged();
         }
 
+        // Before PiP animation is done, th windowing mode of the activity is still the previous
+        // mode (see RootWindowContainer#moveActivityToPinnedRootTask). So once the windowing mode
+        // of activity is changed, it is the signal of the last step to update the PiP states.
+        if (!wasInPictureInPicture && inPinnedWindowingMode() && task != null) {
+            mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, task.getBounds());
+        }
+
         if (display == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 9d225e1..df1fec9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2254,7 +2254,7 @@
         scheduleUpdatePictureInPictureModeIfNeeded(task, rootTask.getRequestedOverrideBounds());
     }
 
-    private void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Rect targetRootTaskBounds) {
+    void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Rect targetRootTaskBounds) {
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityTaskSupervisor::addToPipModeChangedList, this,
                 PooledLambda.__(ActivityRecord.class));
@@ -2278,16 +2278,6 @@
         mMultiWindowModeChangedActivities.remove(r);
     }
 
-    void updatePictureInPictureMode(Task task, Rect targetRootTaskBounds, boolean forceUpdate) {
-        mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
-        final PooledConsumer c = PooledLambda.obtainConsumer(
-                ActivityRecord::updatePictureInPictureMode,
-                PooledLambda.__(ActivityRecord.class), targetRootTaskBounds, forceUpdate);
-        task.getRootTask().setBounds(targetRootTaskBounds);
-        task.forAllActivities(c);
-        c.recycle();
-    }
-
     void wakeUp(String reason) {
         mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION,
                 "android.server.am:TURN_ON:" + reason);
diff --git a/services/core/java/com/android/server/wm/BlurController.java b/services/core/java/com/android/server/wm/BlurController.java
index d920267..ff10168 100644
--- a/services/core/java/com/android/server/wm/BlurController.java
+++ b/services/core/java/com/android/server/wm/BlurController.java
@@ -22,37 +22,32 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.os.PowerManager;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.view.ICrossWindowBlurEnabledListener;
 
-import com.android.internal.annotations.GuardedBy;
-
 /**
  * Keeps track of the different factors that determine whether cross-window blur is enabled
  * or disabled. Also keeps a list of all interested listeners and notifies them when the
  * blur enabled state changes.
  */
 final class BlurController {
-    private final PowerManager mPowerManager;
+    private final Context mContext;
     private final RemoteCallbackList<ICrossWindowBlurEnabledListener>
             mBlurEnabledListeners = new RemoteCallbackList<>();
     // We don't use the WM global lock, because the BlurController is not involved in window
     // drawing and only receives binder calls that don't need synchronization with the rest of WM
     private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    boolean mBlurEnabled;
-    @GuardedBy("mLock")
-    boolean mBlurForceDisabled;
-    @GuardedBy("mLock")
-    boolean mInBatterySaverMode;
+    private volatile boolean mBlurEnabled;
+    private boolean mInPowerSaveMode;
+    private boolean mBlurDisabledSetting;
 
     BlurController(Context context, PowerManager powerManager) {
-        mPowerManager = powerManager;
-        mInBatterySaverMode = mPowerManager.isPowerSaveMode();
-        updateBlurEnabledLocked();
+        mContext = context;
 
         IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
@@ -60,18 +55,36 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
-                    setBatterySaverEnabled(mPowerManager.isPowerSaveMode());
+                    // onReceive always gets called on the same thread, so there is no
+                    // multi-threaded execution here. Thus, we don't have to hold mLock here.
+                    mInPowerSaveMode = powerManager.isPowerSaveMode();
+                    updateBlurEnabled();
                 }
             }
         }, filter, null, null);
+        mInPowerSaveMode = powerManager.isPowerSaveMode();
+
+        context.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.DISABLE_WINDOW_BLURS), false,
+                new ContentObserver(null) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        super.onChange(selfChange);
+                        // onChange always gets called on the same thread, so there is no
+                        // multi-threaded execution here. Thus, we don't have to hold mLock here.
+                        mBlurDisabledSetting = getBlurDisabledSetting();
+                        updateBlurEnabled();
+                    }
+                });
+        mBlurDisabledSetting = getBlurDisabledSetting();
+
+        updateBlurEnabled();
     }
 
     boolean registerCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) {
         if (listener == null) return false;
         mBlurEnabledListeners.register(listener);
-        synchronized (mLock) {
-            return mBlurEnabled;
-        }
+        return getBlurEnabled();
     }
 
     void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) {
@@ -79,31 +92,22 @@
         mBlurEnabledListeners.unregister(listener);
     }
 
-    void setForceCrossWindowBlurDisabled(boolean disable) {
-        synchronized (mLock) {
-            mBlurForceDisabled = disable;
-            updateBlurEnabledLocked();
-        }
-
+    boolean getBlurEnabled() {
+        return mBlurEnabled;
     }
 
-    void setBatterySaverEnabled(boolean enabled) {
+    private void updateBlurEnabled() {
         synchronized (mLock) {
-            mInBatterySaverMode = enabled;
-            updateBlurEnabledLocked();
+            final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurDisabledSetting
+                    && !mInPowerSaveMode;
+            if (mBlurEnabled == newEnabled) {
+                return;
+            }
+            mBlurEnabled = newEnabled;
+            notifyBlurEnabledChangedLocked(newEnabled);
         }
     }
 
-    private void updateBlurEnabledLocked() {
-        final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurForceDisabled
-                && !mInBatterySaverMode;
-        if (mBlurEnabled == newEnabled) {
-            return;
-        }
-        mBlurEnabled = newEnabled;
-        notifyBlurEnabledChangedLocked(newEnabled);
-    }
-
     private void notifyBlurEnabledChangedLocked(boolean enabled) {
         int i = mBlurEnabledListeners.beginBroadcast();
         while (i > 0) {
@@ -117,4 +121,9 @@
         }
         mBlurEnabledListeners.finishBroadcast();
     }
+
+    private boolean getBlurDisabledSetting() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DISABLE_WINDOW_BLURS, 0) == 1;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c44f4e3..0c4e1a2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -154,6 +154,8 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -464,7 +466,7 @@
     private boolean mDeferredRemoval;
 
     final DockedTaskDividerController mDividerControllerLocked;
-    final PinnedTaskController mPinnedTaskControllerLocked;
+    final PinnedTaskController mPinnedTaskController;
 
     final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
     /** A collection of windows that provide tap exclude regions inside of them. */
@@ -1017,7 +1019,7 @@
         }
         mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
         mDividerControllerLocked = new DockedTaskDividerController(this);
-        mPinnedTaskControllerLocked = new PinnedTaskController(mWmService, this);
+        mPinnedTaskController = new PinnedTaskController(mWmService, this);
 
         final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
                 .setOpaque(true)
@@ -1578,10 +1580,6 @@
             // has it own policy for bounds, the activity bounds based on parent is unknown.
             return false;
         }
-        if (mPinnedTaskControllerLocked.isPipActiveOrWindowingModeChanging()) {
-            // Use normal rotation animation because seamless PiP rotation is not supported yet.
-            return false;
-        }
 
         setFixedRotationLaunchingApp(r, rotation);
         return true;
@@ -1667,6 +1665,10 @@
         if (mFixedRotationLaunchingApp == null) {
             return;
         }
+        if (mPinnedTaskController.shouldDeferOrientationChange()) {
+            // Wait for the PiP animation to finish.
+            return;
+        }
         // Update directly because the app which will change the orientation of display is ready.
         if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
             sendNewConfiguration();
@@ -1837,6 +1839,7 @@
             forAllWindows(w -> {
                 w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
             }, true /* traverseTopToBottom */);
+            mPinnedTaskController.startSeamlessRotationIfNeeded(transaction);
         }
 
         mWmService.mDisplayManagerInternal.performTraversal(transaction);
@@ -2333,7 +2336,7 @@
     }
 
     PinnedTaskController getPinnedTaskController() {
-        return mPinnedTaskControllerLocked;
+        return mPinnedTaskController;
     }
 
     /**
@@ -2392,12 +2395,11 @@
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
-        // update resources before cascade so that root docked/pinned tasks use the correct info
-        preOnConfigurationChanged();
         final int lastOrientation = getConfiguration().orientation;
         super.onConfigurationChanged(newParentConfig);
         if (mDisplayPolicy != null) {
             mDisplayPolicy.onConfigurationChanged();
+            mPinnedTaskController.onPostDisplayConfigurationChanged();
         }
 
         if (lastOrientation != getConfiguration().orientation) {
@@ -2408,19 +2410,6 @@
         }
     }
 
-    /**
-     * Updates the resources used by docked/pinned controllers. This needs to be called at the
-     * beginning of a configuration update cascade since the metrics from these resources are used
-     * for bounds calculations.
-     */
-    void preOnConfigurationChanged() {
-        final PinnedTaskController pinnedTaskController = getPinnedTaskController();
-
-        if (pinnedTaskController != null) {
-            getPinnedTaskController().onConfigurationChanged();
-        }
-    }
-
     @Override
     boolean fillsParent() {
         return true;
@@ -2962,7 +2951,7 @@
         final boolean imeVisible = imeWin != null && imeWin.isVisible()
                 && imeWin.isDisplayed();
         final int imeHeight = getInputMethodWindowVisibleHeight();
-        mPinnedTaskControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
+        mPinnedTaskController.setAdjustedForIme(imeVisible, imeHeight);
     }
 
     int getInputMethodWindowVisibleHeight() {
@@ -3190,7 +3179,7 @@
         }
 
         pw.println();
-        mPinnedTaskControllerLocked.dump(prefix, pw);
+        mPinnedTaskController.dump(prefix, pw);
 
         pw.println();
         mDisplayFrames.dump(prefix, pw);
@@ -3821,7 +3810,7 @@
         final ActivityRecord activity = mImeLayeringTarget.mActivityRecord;
         final SurfaceControl imeSurface = mWmService.mSurfaceControlFactory.apply(null)
                 .setName("IME-snapshot-surface")
-                .setBufferSize(buffer.getWidth(), buffer.getHeight())
+                .setBLASTLayer()
                 .setFormat(buffer.getFormat())
                 .setParent(activity.getSurfaceControl())
                 .setCallsite("DisplayContent.attachAndShowImeScreenshotOnTarget")
@@ -3829,10 +3818,9 @@
         // Make IME snapshot as trusted overlay
         InputMonitor.setTrustedOverlayInputInfo(imeSurface, t, getDisplayId(),
                 "IME-snapshot-surface");
-        Surface surface = mWmService.mSurfaceFactory.get();
-        surface.copyFrom(imeSurface);
-        surface.attachAndQueueBufferWithColorSpace(buffer, null);
-        surface.release();
+        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(buffer);
+        t.setBuffer(imeSurface, graphicBuffer);
+        t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
         t.setRelativeLayer(imeSurface, activity.getSurfaceControl(), 1);
         t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left,
                 mInputMethodWindow.getDisplayFrame().top);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index e1fc75e..0e73d79 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -254,7 +254,7 @@
 
         if (isDefaultDisplay) {
             final Handler uiHandler = UiThread.getHandler();
-            mOrientationListener = new OrientationListener(mContext, uiHandler, mService);
+            mOrientationListener = new OrientationListener(mContext, uiHandler);
             mOrientationListener.setCurrentRotation(mRotation);
             mSettingsObserver = new SettingsObserver(uiHandler);
             mSettingsObserver.observe();
@@ -1514,8 +1514,8 @@
         final SparseArray<Runnable> mRunnableCache = new SparseArray<>(5);
         boolean mEnabled;
 
-        OrientationListener(Context context, Handler handler, WindowManagerService service) {
-            super(context, handler, service);
+        OrientationListener(Context context, Handler handler) {
+            super(context, handler);
         }
 
         private class UpdateRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 15e078b..dea83f0 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -16,20 +16,26 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.app.PictureInPictureParams;
 import android.app.RemoteAction;
 import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Slog;
-import android.view.DisplayInfo;
 import android.view.IPinnedTaskListener;
+import android.view.SurfaceControl;
+import android.window.PictureInPictureSurfaceTransaction;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -54,6 +60,7 @@
 class PinnedTaskController {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedTaskController" : TAG_WM;
+    private static final int DEFER_ORIENTATION_CHANGE_TIMEOUT_MS = 1000;
 
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
@@ -62,8 +69,21 @@
     private final PinnedTaskListenerDeathHandler mPinnedTaskListenerDeathHandler =
             new PinnedTaskListenerDeathHandler();
 
-    /** Whether the PiP is entering or leaving. */
-    private boolean mIsPipWindowingModeChanging;
+    /**
+     * Non-null if the entering PiP task will cause display rotation to change. The bounds are
+     * based on the new rotation.
+     */
+    private Rect mDestRotatedBounds;
+    /**
+     * Non-null if the entering PiP task from recents animation will cause display rotation to
+     * change. The transaction is based on the old rotation.
+     */
+    private PictureInPictureSurfaceTransaction mPipTransaction;
+    /** Whether to skip task configuration change once. */
+    private boolean mFreezingTaskConfig;
+    /** Defer display orientation change if the PiP task is animating across orientations. */
+    private boolean mDeferOrientationChanging;
+    private final Runnable mDeferOrientationTimeoutRunnable;
 
     private boolean mIsImeShowing;
     private int mImeHeight;
@@ -72,16 +92,10 @@
     private ArrayList<RemoteAction> mActions = new ArrayList<>();
     private float mAspectRatio = -1f;
 
-    // Used to calculate task bounds across rotations
-    private final DisplayInfo mDisplayInfo = new DisplayInfo();
-
     // The aspect ratio bounds of the PIP.
     private float mMinAspectRatio;
     private float mMaxAspectRatio;
 
-    // Temp vars for calculation
-    private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
-
     /**
      * Handler for the case where the listener dies.
      */
@@ -89,23 +103,32 @@
 
         @Override
         public void binderDied() {
-            // Clean up the state if the listener dies
-            if (mPinnedTaskListener != null) {
-                mPinnedTaskListener.asBinder().unlinkToDeath(mPinnedTaskListenerDeathHandler, 0);
+            synchronized (mService.mGlobalLock) {
+                mPinnedTaskListener = null;
+                mFreezingTaskConfig = false;
+                mDeferOrientationTimeoutRunnable.run();
             }
-            mPinnedTaskListener = null;
         }
     }
 
     PinnedTaskController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
-        mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
+        mDeferOrientationTimeoutRunnable = () -> {
+            synchronized (mService.mGlobalLock) {
+                if (mDeferOrientationChanging) {
+                    continueOrientationChange();
+                    mService.mWindowPlacerLocked.requestTraversal();
+                }
+            }
+        };
         reloadResources();
     }
 
-    void onConfigurationChanged() {
+    /** Updates the resources used by pinned controllers.  */
+    void onPostDisplayConfigurationChanged() {
         reloadResources();
+        mFreezingTaskConfig = false;
     }
 
     /**
@@ -113,7 +136,6 @@
      */
     private void reloadResources() {
         final Resources res = mService.mContext.getResources();
-        mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
         mMinAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
         mMaxAspectRatio = res.getFloat(
@@ -143,18 +165,150 @@
                 && Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
     }
 
-    /** Returns {@code true} if the PiP is on screen or is changing windowing mode. */
-    boolean isPipActiveOrWindowingModeChanging() {
-        if (mIsPipWindowingModeChanging) {
-            return true;
+    /**
+     * Called when a fullscreen task is entering PiP with display orientation change. This is used
+     * to avoid flickering when running PiP animation across different orientations.
+     */
+    void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() {
+        final Task topFullscreenTask = mDisplayContent.getDefaultTaskDisplayArea()
+                .getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        final ActivityRecord topFullscreen = topFullscreenTask != null
+                ? topFullscreenTask.topRunningActivity() : null;
+        if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) {
+            return;
         }
-        final Task pinnedTask = mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask();
-        return pinnedTask != null && pinnedTask.hasChild();
+        final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(
+                topFullscreen);
+        if (rotation == ROTATION_UNDEFINED) {
+            return;
+        }
+        // If the next top activity will change the orientation of display, start fixed rotation to
+        // notify PipTaskOrganizer before it receives task appeared. And defer display orientation
+        // update until the new PiP bounds are set.
+        mDisplayContent.setFixedRotationLaunchingApp(topFullscreen, rotation);
+        mDeferOrientationChanging = true;
+        mService.mH.removeCallbacks(mDeferOrientationTimeoutRunnable);
+        final float animatorScale = Math.max(1, mService.getCurrentAnimatorScale());
+        mService.mH.postDelayed(mDeferOrientationTimeoutRunnable,
+                (int) (animatorScale * DEFER_ORIENTATION_CHANGE_TIMEOUT_MS));
     }
 
-    /** Sets whether a visible task is changing from or to pinned mode. */
-    void setPipWindowingModeChanging(boolean isPipWindowingModeChanging) {
-        mIsPipWindowingModeChanging = isPipWindowingModeChanging;
+    /** Defers orientation change while there is a top fixed rotation activity. */
+    boolean shouldDeferOrientationChange() {
+        return mDeferOrientationChanging;
+    }
+
+    /**
+     * Sets the bounds for {@link #startSeamlessRotationIfNeeded} if the orientation of display
+     * will be changed.
+     */
+    void setEnterPipBounds(Rect bounds) {
+        if (!mDeferOrientationChanging) {
+            return;
+        }
+        mFreezingTaskConfig = true;
+        mDestRotatedBounds = new Rect(bounds);
+        continueOrientationChange();
+    }
+
+    /**
+     * Sets the transaction for {@link #startSeamlessRotationIfNeeded} if the orientation of display
+     * will be changed. This is only called when finishing recents animation with pending
+     * orientation change that will be handled by
+     * {@link DisplayContent.FixedRotationTransitionListener#onFinishRecentsAnimation}.
+     */
+    void setEnterPipTransaction(PictureInPictureSurfaceTransaction tx) {
+        mFreezingTaskConfig = true;
+        mPipTransaction = tx;
+    }
+
+    /** Called when the activity in PiP task has PiP windowing mode (at the end of animation). */
+    private void continueOrientationChange() {
+        mDeferOrientationChanging = false;
+        mService.mH.removeCallbacks(mDeferOrientationTimeoutRunnable);
+        final WindowContainer<?> orientationSource = mDisplayContent.getLastOrientationSource();
+        if (orientationSource != null && !orientationSource.isAppTransitioning()) {
+            mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+        }
+    }
+
+    /**
+     * Resets rotation and applies scale and position to PiP task surface to match the current
+     * rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it
+     * receives the callback of fixed rotation completion.
+     */
+    void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t) {
+        final Rect bounds = mDestRotatedBounds;
+        final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
+        if (bounds == null && pipTx == null) {
+            return;
+        }
+        final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea();
+        final Task pinnedTask = taskArea.getRootPinnedTask();
+        if (pinnedTask == null) {
+            return;
+        }
+
+        mDestRotatedBounds = null;
+        mPipTransaction = null;
+        final Rect areaBounds = taskArea.getBounds();
+        if (pipTx != null) {
+            // The transaction from recents animation is in old rotation. So the position needs to
+            // be rotated.
+            float dx = pipTx.mPositionX;
+            float dy = pipTx.mPositionY;
+            if (pipTx.mRotation == 90) {
+                dx = pipTx.mPositionY;
+                dy = areaBounds.right - pipTx.mPositionX;
+            } else if (pipTx.mRotation == -90) {
+                dx = areaBounds.bottom - pipTx.mPositionY;
+                dy = pipTx.mPositionX;
+            }
+            final Matrix matrix = new Matrix();
+            matrix.setScale(pipTx.mScaleX, pipTx.mScaleY);
+            matrix.postTranslate(dx, dy);
+            t.setMatrix(pinnedTask.getSurfaceControl(), matrix, new float[9]);
+            Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
+            return;
+        }
+
+        final PictureInPictureParams params = pinnedTask.getPictureInPictureParams();
+        final Rect sourceHintRect = params != null && params.hasSourceBoundsHint()
+                ? params.getSourceRectHint()
+                : null;
+        Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect);
+        final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect)
+                ? sourceHintRect : areaBounds;
+        final int w = contentBounds.width();
+        final int h = contentBounds.height();
+        final float scale = w <= h ? (float) bounds.width() / w : (float) bounds.height() / h;
+        final int insetLeft = (int) ((contentBounds.left - areaBounds.left) * scale + .5f);
+        final int insetTop = (int) ((contentBounds.top - areaBounds.top) * scale + .5f);
+        final Matrix matrix = new Matrix();
+        matrix.setScale(scale, scale);
+        matrix.postTranslate(bounds.left - insetLeft, bounds.top - insetTop);
+        t.setMatrix(pinnedTask.getSurfaceControl(), matrix, new float[9]);
+    }
+
+    /**
+     * Returns {@code true} to skip {@link Task#onConfigurationChanged} because it is expected that
+     * there will be a orientation change and a PiP configuration change.
+     */
+    boolean isFreezingTaskConfig(Task task) {
+        return mFreezingTaskConfig
+                && task == mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask();
+    }
+
+    /** Resets the states which were used to perform fixed rotation with PiP task. */
+    void onCancelFixedRotationTransform(Task task) {
+        mFreezingTaskConfig = false;
+        mDeferOrientationChanging = false;
+        mDestRotatedBounds = null;
+        mPipTransaction = null;
+        if (!task.isOrganized()) {
+            // Force clearing Task#mForceNotOrganized because the display didn't rotate.
+            task.onConfigurationChanged(task.getParent().getConfiguration());
+        }
     }
 
     /**
@@ -272,6 +426,14 @@
 
     void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "PinnedTaskController");
+        if (mDeferOrientationChanging) pw.println(prefix + "  mDeferOrientationChanging=true");
+        if (mFreezingTaskConfig) pw.println(prefix + "  mFreezingTaskConfig=true");
+        if (mDestRotatedBounds != null) {
+            pw.println(prefix + "  mPendingBounds=" + mDestRotatedBounds);
+        }
+        if (mPipTransaction != null) {
+            pw.println(prefix + "  mPipTransaction=" + mPipTransaction);
+        }
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
         pw.println(prefix + "  mImeHeight=" + mImeHeight);
         pw.println(prefix + "  mAspectRatio=" + mAspectRatio);
@@ -288,6 +450,5 @@
             }
             pw.println(prefix + "  ]");
         }
-        pw.println(prefix + "  mDisplayInfo=" + mDisplayInfo);
     }
 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index e165dfa..dec6460 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -1146,6 +1146,11 @@
                 PictureInPictureSurfaceTransaction.apply(mFinishTransaction,
                         mTask.mSurfaceControl, pendingTransaction);
                 mTask.setLastRecentsAnimationTransaction(mFinishTransaction);
+                if (mDisplayContent.isFixedRotationLaunchingApp(mTargetActivityRecord)) {
+                    // The transaction is needed for position when rotating the display.
+                    mDisplayContent.mPinnedTaskController.setEnterPipTransaction(
+                            mFinishTransaction);
+                }
                 mFinishTransaction = null;
                 pendingTransaction.apply();
             } else if (!mTask.isAttached()) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d56d73a..d4707d6f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -176,12 +176,14 @@
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -2267,7 +2269,6 @@
         }
 
         if (pipChanging) {
-            mDisplayContent.getPinnedTaskController().setPipWindowingModeChanging(true);
             // If the top activity is using fixed rotation, it should be changing from PiP to
             // fullscreen with display orientation change. Do not notify fullscreen task organizer
             // because the restoration of task surface and the transformation of activity surface
@@ -2276,29 +2277,15 @@
             if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) {
                 mForceNotOrganized = true;
             }
-        } else if (mForceNotOrganized) {
+        } else {
             // If the display orientation change is done, let the corresponding task organizer take
             // back the control of this task.
-            final ActivityRecord r = topRunningActivity();
-            if (r == null || !mDisplayContent.isFixedRotationLaunchingApp(r)) {
-                mForceNotOrganized = false;
-            }
+            mForceNotOrganized = false;
         }
-        try {
-            // We have 2 reasons why we need to report orientation change here.
-            // 1. In some cases (e.g. freeform -> fullscreen) we don't have other ways of reporting.
-            // 2. Report orientation as soon as possible so that the display can freeze earlier if
-            // the display orientation will be changed. Because the surface bounds of activity
-            // may have been set to fullscreen but the activity hasn't redrawn its content yet,
-            // the rotation animation needs to capture snapshot earlier to avoid animating from
-            // an intermediate state.
-            if (oldOrientation != getOrientation()) {
-                onDescendantOrientationChanged(this);
-            }
-        } finally {
-            if (pipChanging) {
-                mDisplayContent.getPinnedTaskController().setPipWindowingModeChanging(false);
-            }
+
+        // Report orientation change such as changing from freeform to fullscreen.
+        if (oldOrientation != getOrientation()) {
+            onDescendantOrientationChanged(this);
         }
 
         saveLaunchingStateIfNeeded();
@@ -2321,6 +2308,15 @@
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
+        if (mDisplayContent != null
+                && mDisplayContent.mPinnedTaskController.isFreezingTaskConfig(this)) {
+            // It happens when animating from fullscreen to PiP with orientation change. Because
+            // the activity in this pinned task is in fullscreen windowing mode (see
+            // RootWindowContainer#moveActivityToPinnedRootTask) and the activity will be set to
+            // pinned mode after the animation is done, the configuration change by orientation
+            // change is just an intermediate state that should be ignored to avoid flickering.
+            return;
+        }
         // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are
         // particularly for root tasks, like preventing bounds changes when inheriting certain
         // windowing mode.
@@ -4471,10 +4467,10 @@
         pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture);
         pw.print(" isResizeable="); pw.println(isResizeable());
         pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
+        pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
         if (mForceNotOrganized) {
             pw.print(prefix); pw.println("mForceNotOrganized=true");
         }
-        pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
     }
 
     @Override
@@ -5437,6 +5433,13 @@
                 // Nothing else to do if we don't have a window container yet. E.g. call from ctor.
                 return;
             }
+
+            // From fullscreen to PiP.
+            if (topActivity != null && currentMode == WINDOWING_MODE_FULLSCREEN
+                    && windowingMode == WINDOWING_MODE_PINNED) {
+                mDisplayContent.mPinnedTaskController
+                        .deferOrientationChangeForEnteringPipFromFullScreenIfNeeded();
+            }
         } finally {
             mAtmService.continueWindowLayout();
         }
@@ -6676,8 +6679,30 @@
                         prev = null;
                     }
                 }
-                final int splashScreenThemeResId = options != null
+
+                // TODO(185200798): Persist theme name instead of theme if
+                int splashScreenThemeResId = options != null
                         ? options.getSplashScreenThemeResId() : 0;
+
+                // User can override the splashscreen theme. The theme name is used to persist
+                // the setting, so if no theme is set in the ActivityOptions, we check if has
+                // been persisted here.
+                if (splashScreenThemeResId == 0) {
+                    try {
+                        String themeName = mAtmService.getPackageManager()
+                                .getSplashScreenTheme(r.packageName, r.mUserId);
+                        if (themeName != null) {
+                            Context packageContext = mAtmService.mContext
+                                    .createPackageContext(r.packageName, 0);
+                            splashScreenThemeResId = packageContext.getResources()
+                                    .getIdentifier(themeName, null, null);
+                        }
+                    } catch (RemoteException | PackageManager.NameNotFoundException
+                            | Resources.NotFoundException ignore) {
+                        // Just use the default theme
+                    }
+                }
+
                 r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
                         splashScreenThemeResId, samePackage);
             }
diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
index 1779d2a..7e992ac 100644
--- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
+++ b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
@@ -17,8 +17,8 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 
+import android.graphics.GraphicBuffer;
 import android.hardware.HardwareBuffer;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
@@ -51,14 +51,14 @@
                         task, mWidth, mHeight);
         mSurfaceControl = surfaceControlFactory.apply(new SurfaceSession())
                 .setName("RecentTaskScreenshotSurface")
-                .setBufferSize(mWidth, mHeight)
+                .setBLASTLayer()
                 .setCallsite("TaskScreenshotAnimatable")
                 .build();
         if (buffer != null) {
-            final Surface surface = new Surface();
-            surface.copyFrom(mSurfaceControl);
-            surface.attachAndQueueBufferWithColorSpace(buffer, screenshotBuffer.getColorSpace());
-            surface.release();
+            GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(buffer);
+            getPendingTransaction().setBuffer(mSurfaceControl, graphicBuffer);
+            getPendingTransaction().setColorSpace(mSurfaceControl,
+                    screenshotBuffer.getColorSpace());
             final float scale = 1.0f * mTask.getBounds().width() / mWidth;
             getPendingTransaction().setMatrix(mSurfaceControl, scale, 0, 0, scale);
         }
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index b9f67a5..7f21eeb 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -28,12 +28,13 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.hardware.HardwareBuffer;
 import android.os.Process;
 import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceControl.Transaction;
@@ -43,8 +44,6 @@
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 
-import java.util.function.Supplier;
-
 /**
  * Represents a surface that is displayed over a subclass of {@link WindowContainer}
  */
@@ -57,30 +56,20 @@
     private final SurfaceAnimator mSurfaceAnimator;
     private final int mWidth;
     private final int mHeight;
-    private final boolean mRelative;
-
-    WindowContainerThumbnail(Supplier<Surface> surfaceFactory, Transaction t,
-            WindowContainer container, HardwareBuffer thumbnailHeader) {
-        this(surfaceFactory, t, container, thumbnailHeader, false /* relative */);
-    }
 
     /**
      * @param t Transaction to create the thumbnail in.
      * @param container The sub-class of {@link WindowContainer} to associate this thumbnail with.
      * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
-     * @param relative Whether this thumbnail will be a child of the container (and thus positioned
-     *                 relative to it) or not.
      */
-    WindowContainerThumbnail(Supplier<Surface> surfaceFactory, Transaction t,
-            WindowContainer container, HardwareBuffer thumbnailHeader, boolean relative) {
-        this(t, container, thumbnailHeader, relative, surfaceFactory.get(), null);
+    WindowContainerThumbnail(Transaction t, WindowContainer container,
+            HardwareBuffer thumbnailHeader) {
+        this(t, container, thumbnailHeader, null /* animator */);
     }
 
     WindowContainerThumbnail(Transaction t, WindowContainer container,
-            HardwareBuffer thumbnailHeader, boolean relative, Surface drawSurface,
-            SurfaceAnimator animator) {
+            HardwareBuffer thumbnailHeader, SurfaceAnimator animator) {
         mWindowContainer = container;
-        mRelative = relative;
         if (animator != null) {
             mSurfaceAnimator = animator;
         } else {
@@ -99,7 +88,7 @@
         // this to the task.
         mSurfaceControl = mWindowContainer.makeChildSurface(mWindowContainer.getTopChild())
                 .setName("thumbnail anim: " + mWindowContainer.toString())
-                .setBufferSize(mWidth, mHeight)
+                .setBLASTLayer()
                 .setFormat(PixelFormat.TRANSLUCENT)
                 .setMetadata(METADATA_WINDOW_TYPE, mWindowContainer.getWindowingMode())
                 .setMetadata(METADATA_OWNER_UID, Process.myUid())
@@ -108,18 +97,14 @@
 
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "  THUMBNAIL %s: CREATE", mSurfaceControl);
 
-        // Transfer the thumbnail to the surface
-        drawSurface.copyFrom(mSurfaceControl);
-        drawSurface.attachAndQueueBufferWithColorSpace(thumbnailHeader, null);
-        drawSurface.release();
+        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(thumbnailHeader);
+        t.setBuffer(mSurfaceControl, graphicBuffer);
+        t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
         t.show(mSurfaceControl);
 
         // We parent the thumbnail to the container, and just place it on top of anything else in
         // the container.
         t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
-        if (relative) {
-            t.reparent(mSurfaceControl, mWindowContainer.getSurfaceControl());
-        }
     }
 
     void startAnimation(Transaction t, Animation anim) {
@@ -194,9 +179,6 @@
     @Override
     public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
         t.setLayer(leash, Integer.MAX_VALUE);
-        if (mRelative) {
-            t.reparent(leash, mWindowContainer.getSurfaceControl());
-        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index 015a0fb..a5ebf9a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -49,10 +49,6 @@
     static final String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
             "system_gesture_exclusion_log_debounce_millis";
 
-    // Enable logging from the sensor which publishes accel and gyro data generating a rotation
-    // event
-    private static final String KEY_RAW_SENSOR_LOGGING_ENABLED = "raw_sensor_logging_enabled";
-
     private static final int MIN_GESTURE_EXCLUSION_LIMIT_DP = 200;
 
     /** @see #KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS */
@@ -62,8 +58,6 @@
     /** @see AndroidDeviceConfig#KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE */
     boolean mSystemGestureExcludedByPreQStickyImmersive;
 
-    boolean mRawSensorLoggingEnabled;
-
     private final WindowManagerGlobalLock mGlobalLock;
     private final Runnable mUpdateSystemGestureExclusionCallback;
     private final DeviceConfigInterface mDeviceConfig;
@@ -139,9 +133,6 @@
                     case KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS:
                         updateSystemGestureExclusionLogDebounceMillis();
                         break;
-                    case KEY_RAW_SENSOR_LOGGING_ENABLED:
-                        updateRawSensorDataLoggingEnabled();
-                        break;
                     default:
                         break;
                 }
@@ -167,12 +158,6 @@
                 KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
     }
 
-    private void updateRawSensorDataLoggingEnabled() {
-        mRawSensorLoggingEnabled = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                KEY_RAW_SENSOR_LOGGING_ENABLED, false);
-    }
-
     void dump(PrintWriter pw) {
         pw.println("WINDOW MANAGER CONSTANTS (dumpsys window constants):");
 
@@ -182,8 +167,6 @@
         pw.print("="); pw.println(mSystemGestureExclusionLimitDp);
         pw.print("  "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
         pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
-        pw.print("  "); pw.print(KEY_RAW_SENSOR_LOGGING_ENABLED);
-        pw.print("="); pw.println(mRawSensorLoggingEnabled);
         pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3d84ca7..9e8b6a3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -451,8 +451,7 @@
 
     private static final int ANIMATION_COMPLETED_TIMEOUT_MS = 5000;
 
-    @VisibleForTesting
-    WindowManagerConstants mConstants;
+    final WindowManagerConstants mConstants;
 
     final WindowTracing mWindowTracing;
 
@@ -5554,11 +5553,6 @@
         mBlurController.unregisterCrossWindowBlurEnabledListener(listener);
     }
 
-    @Override
-    public void setForceCrossWindowBlurDisabled(boolean disable) {
-        mBlurController.setForceCrossWindowBlurDisabled(disable);
-    }
-
     // -------------------------------------------------------------
     // Internals
     // -------------------------------------------------------------
@@ -6298,7 +6292,7 @@
             }
         });
         pw.print("  mInTouchMode="); pw.println(mInTouchMode);
-        pw.print("  mBlurEnabled="); pw.println(mBlurController.mBlurEnabled);
+        pw.print("  mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
         pw.print("  mLastDisplayFreezeDuration=");
                 TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
                 if ( mLastFinishedFreezeSource != null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 1b578d1..4dc6007 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -31,6 +31,7 @@
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Pair;
 import android.view.Display;
@@ -216,7 +217,7 @@
         String arg = getNextArg();
         if (arg == null) {
             pw.println("Blur supported on device: " + CROSS_WINDOW_BLUR_SUPPORTED);
-            pw.println("Blur enabled: " + mInternal.mBlurController.mBlurEnabled);
+            pw.println("Blur enabled: " + mInternal.mBlurController.getBlurEnabled());
             return 0;
         }
 
@@ -235,7 +236,9 @@
                 return -1;
         }
 
-        mInterface.setForceCrossWindowBlurDisabled(disableBlur);
+        Settings.Global.putInt(mInternal.mContext.getContentResolver(),
+                Settings.Global.DISABLE_WINDOW_BLURS, disableBlur ? 1 : 0);
+
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9382b8e..12a6a54 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -491,7 +491,7 @@
 
         Rect enterPipBounds = c.getEnterPipBounds();
         if (enterPipBounds != null) {
-            mService.mTaskSupervisor.updatePictureInPictureMode(tr, enterPipBounds, true);
+            tr.mDisplayContent.mPinnedTaskController.setEnterPipBounds(enterPipBounds);
         }
 
         return effects;
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index be6847ab..3e099fb 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -85,7 +85,6 @@
 
     private int mCurrentRotation = -1;
     private final Context mContext;
-    private final WindowManagerConstants mConstants;
 
     private final Object mLock = new Object();
 
@@ -94,11 +93,9 @@
      *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
-     * @param wmService WindowManagerService to read the device config from.
      */
-    public WindowOrientationListener(
-            Context context, Handler handler, WindowManagerService wmService) {
-        this(context, handler, wmService, SensorManager.SENSOR_DELAY_UI);
+    public WindowOrientationListener(Context context, Handler handler) {
+        this(context, handler, SensorManager.SENSOR_DELAY_UI);
     }
 
     /**
@@ -115,10 +112,9 @@
      * This constructor is private since no one uses it.
      */
     private WindowOrientationListener(
-            Context context, Handler handler, WindowManagerService wmService, int rate) {
+            Context context, Handler handler, int rate) {
         mContext = context;
         mHandler = handler;
-        mConstants = wmService.mConstants;
         mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
         mRate = rate;
         List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
@@ -1134,16 +1130,11 @@
                 return;
             }
 
-            // Log raw sensor rotation.
-            if (evaluateRotationChangeLocked() >= 0) {
-                if (mConstants.mRawSensorLoggingEnabled) {
-                    FrameworkStatsLog.write(
-                            FrameworkStatsLog.DEVICE_ROTATED,
-                            event.timestamp,
-                            rotationToLogEnum(reportedRotation),
-                            FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
-                }
-            }
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.DEVICE_ROTATED,
+                    event.timestamp,
+                    rotationToLogEnum(reportedRotation),
+                    FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
 
             if (isRotationResolverEnabled()) {
                 if (mRotationResolverService == null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 83f74cd..f71d08a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -906,6 +906,12 @@
             // The transform of its surface is handled by fixed rotation.
             return;
         }
+        final Task task = getTask();
+        if (task != null && task.inPinnedWindowingMode()) {
+            // It is handled by PinnedTaskController. Note that the windowing mode of activity
+            // and windows may still be fullscreen.
+            return;
+        }
 
         if (mPendingSeamlessRotate != null) {
             oldRotation = mPendingSeamlessRotate.getOldRotation();
@@ -5348,7 +5354,8 @@
     }
 
     private boolean shouldDrawBlurBehind() {
-        return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mWmService.mBlurController.mBlurEnabled;
+        return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0
+            && mWmService.mBlurController.getBlurEnabled();
     }
 
     /**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 64cdfa6..8274e38 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4507,8 +4507,7 @@
 
             PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(parentUser);
             final List<PasswordValidationError> passwordValidationErrors =
-                    PasswordMetrics.validatePasswordMetrics(
-                            minMetrics, complexity, false, metrics);
+                    PasswordMetrics.validatePasswordMetrics(minMetrics, complexity, metrics);
             isSufficient = passwordValidationErrors.isEmpty();
         }
         DevicePolicyEventLogger
@@ -4585,7 +4584,7 @@
                 maxRequiredComplexity = Math.max(maxRequiredComplexity, admin.mPasswordComplexity);
             }
             return PasswordMetrics.validatePasswordMetrics(PasswordMetrics.merge(adminMetrics),
-                    maxRequiredComplexity, false, metrics).isEmpty();
+                    maxRequiredComplexity, metrics).isEmpty();
         }
     }
 
@@ -4621,8 +4620,7 @@
         final int complexity = getAggregatedPasswordComplexityLocked(userId);
         PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(userId);
         final List<PasswordValidationError> passwordValidationErrors =
-                PasswordMetrics.validatePasswordMetrics(
-                        minMetrics, complexity, false, metrics);
+                PasswordMetrics.validatePasswordMetrics(minMetrics, complexity, metrics);
         return passwordValidationErrors.isEmpty();
     }
 
@@ -4969,8 +4967,7 @@
             // TODO: Consider changing validation API to take LockscreenCredential.
             if (password.isEmpty()) {
                 validationErrors = PasswordMetrics.validatePasswordMetrics(
-                        minMetrics, complexity, isPin,
-                        new PasswordMetrics(CREDENTIAL_TYPE_NONE));
+                        minMetrics, complexity, new PasswordMetrics(CREDENTIAL_TYPE_NONE));
             } else {
                 // TODO(b/120484642): remove getBytes() below
                 validationErrors = PasswordMetrics.validatePassword(
@@ -16774,7 +16771,9 @@
                     provisioningParams.isKeepAccountMigrated(), callerPackage);
 
             if (provisioningParams.isOrganizationOwnedProvisioning()) {
-                setProfileOwnerOnOrgOwnedDeviceState(admin, userInfo.id, caller.getUserId());
+                synchronized (getLockObject()) {
+                    markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, userInfo.id);
+                }
             }
 
             return userInfo.getUserHandle();
@@ -17006,22 +17005,6 @@
         }
     }
 
-    private void setProfileOwnerOnOrgOwnedDeviceState(
-            ComponentName admin, @UserIdInt int profileId, @UserIdInt int parentUserId) {
-        synchronized (getLockObject()) {
-            markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, profileId);
-        }
-        restrictRemovalOfManagedProfile(parentUserId);
-    }
-
-    private void restrictRemovalOfManagedProfile(@UserIdInt int parentUserId) {
-        final UserHandle parentUserHandle = UserHandle.of(parentUserId);
-        mUserManager.setUserRestriction(
-                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
-                /* value= */ true,
-                parentUserHandle);
-    }
-
     @Override
     public void provisionFullyManagedDevice(
             @NonNull FullyManagedDeviceProvisioningParams provisioningParams,
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerUtilsTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerUtilsTest.java
new file mode 100644
index 0000000..96103e3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerUtilsTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.am;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class ActivityManagerUtilsTest {
+    @Test
+    public void getAndroidIdHash() {
+        // getAndroidIdHash() essentially returns a random a value. Just make sure it's
+        // non-negative.
+        assertThat(ActivityManagerUtils.getAndroidIdHash()).isAtLeast(0);
+    }
+
+    @Test
+    public void getUnsignedHashCached() {
+        assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isEqualTo(
+                ActivityManagerUtils.getUnsignedHashCached("x"));
+
+        assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isNotEqualTo(
+                ActivityManagerUtils.getUnsignedHashCached("y"));
+    }
+
+    @Test
+    public void shouldSamplePackage_sampleNone() {
+        final int numTests = 100000;
+        for (int i = 0; i < numTests; i++) {
+            assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 0))
+                    .isFalse();
+        }
+    }
+
+    @Test
+    public void shouldSamplePackage_sampleAll() {
+        final int numTests = 100000;
+
+        for (int i = 0; i < numTests; i++) {
+            assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 1))
+                    .isTrue();
+        }
+    }
+
+    /**
+     * Make sure, with the same android ID, an expected rate of the packages are selected.
+     */
+    @Test
+    public void shouldSamplePackage_sampleSome_fixedAndroidId() {
+        checkShouldSamplePackage_fixedAndroidId(0.1f);
+        checkShouldSamplePackage_fixedAndroidId(0.5f);
+        checkShouldSamplePackage_fixedAndroidId(0.9f);
+    }
+
+    /**
+     * Make sure, the same package is selected on an expected rate of the devices.
+     */
+    @Test
+    public void shouldSamplePackage_sampleSome_fixedPackage() {
+        checkShouldSamplePackage_fixedPackage(0.1f);
+        checkShouldSamplePackage_fixedPackage(0.5f);
+        checkShouldSamplePackage_fixedPackage(0.9f);
+    }
+
+    private void checkShouldSamplePackage_fixedPackage(float sampleRate) {
+        checkShouldSamplePackage(sampleRate, sampleRate, true, false);
+    }
+
+    private void checkShouldSamplePackage_fixedAndroidId(float sampleRate) {
+        checkShouldSamplePackage(sampleRate, sampleRate, false, true);
+    }
+
+    @Test
+    public void testSheckShouldSamplePackage() {
+        // Just make sure checkShouldSamplePackage is actually working...
+        try {
+            checkShouldSamplePackage(0.3f, 0.6f, false, true);
+            fail();
+        } catch (AssertionError expected) {
+        }
+        try {
+            checkShouldSamplePackage(0.6f, 0.3f, true, false);
+            fail();
+        } catch (AssertionError expected) {
+        }
+    }
+
+    private void checkShouldSamplePackage(float inputSampleRate, float expectedRate,
+            boolean fixedPackage, boolean fixedAndroidId) {
+        final int numTests = 100000;
+
+        try {
+            int numSampled = 0;
+            for (int i = 0; i < numTests; i++) {
+                final String pkg = fixedPackage ? "fixed-package" : "" + i;
+                ActivityManagerUtils.injectAndroidIdForTest(
+                        fixedAndroidId ? "fixed-android-id" : "" + i);
+
+                if (ActivityManagerUtils.shouldSamplePackageForAtom(pkg, inputSampleRate)) {
+                    numSampled++;
+                }
+                assertThat(ActivityManagerUtils.getUnsignedHashCached(pkg)).isEqualTo(
+                        ActivityManagerUtils.getUnsignedHashCached(pkg));
+            }
+            final double actualSampleRate = ((double) numSampled) / numTests;
+
+            assertThat(actualSampleRate).isWithin(0.05).of(expectedRate);
+        } finally {
+            ActivityManagerUtils.injectAndroidIdForTest(null);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 7cd6028..f156779 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -93,9 +93,10 @@
     private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
     private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
     private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
-    private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+    private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
                     | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
                     | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
@@ -764,7 +765,8 @@
 
         // register display listener callback
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        displayManagerBinderService.registerCallbackWithEventMask(callback, ALL_DISPLAY_EVENTS);
+        displayManagerBinderService.registerCallbackWithEventMask(
+                callback, STANDARD_DISPLAY_EVENTS);
 
         waitForIdleHandler(handler);
 
@@ -793,7 +795,7 @@
 
         // register display listener callback
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        long allEventsExceptDisplayAdded = ALL_DISPLAY_EVENTS
+        long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
                 & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
         displayManagerBinderService.registerCallbackWithEventMask(callback,
                 allEventsExceptDisplayAdded);
@@ -862,7 +864,7 @@
         waitForIdleHandler(handler);
 
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        long allEventsExceptDisplayRemoved = ALL_DISPLAY_EVENTS
+        long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
                 & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
         displayManagerBinderService.registerCallbackWithEventMask(callback,
                 allEventsExceptDisplayRemoved);
@@ -1032,7 +1034,8 @@
 
         // register display listener callback
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId);
-        displayManagerBinderService.registerCallbackWithEventMask(callback, ALL_DISPLAY_EVENTS);
+        displayManagerBinderService.registerCallbackWithEventMask(
+                callback, STANDARD_DISPLAY_EVENTS);
         return callback;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
new file mode 100644
index 0000000..88a21b4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.display;
+
+import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
+import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class HighBrightnessModeControllerTest {
+
+    private static final int MINIMUM_LUX = 100;
+    private static final float TRANSITION_POINT = 0.763f;
+    private static final long TIME_WINDOW_MILLIS = 55 * 1000;
+    private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
+    private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
+
+    private static final float DEFAULT_MIN = 0.01f;
+    private static final float DEFAULT_MAX = 0.80f;
+
+    private static final float EPSILON = 0.000001f;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+
+    private static final HighBrightnessModeData DEFAULT_HBM_DATA =
+            new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
+                    TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS);
+
+    @Before
+    public void setUp() {
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message msg) {
+                return true;
+            }
+        });
+    }
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testNoHbmData() {
+        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
+                mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX, null, () -> {});
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testNoHbmData_Enabled() {
+        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
+                mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX, null, () -> {});
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testOffByDefault() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_NoLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_LowLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_HighLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_HighLux_ThenDisable() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.setAutoBrightnessEnabled(false);
+
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testWithinHighRange_thenOverTime_thenEarnBackTime() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Use up all the time in the window.
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS);
+
+        // Verify we are not out of HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Shift time so that the HBM event is at the beginning of the current window
+        advanceTime(TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS);
+        // Shift time again so that we are just below the minimum allowable
+        advanceTime(TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS - 1);
+
+        // Verify we are not out of HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Advance the necessary millisecond
+        advanceTime(1);
+
+        // Verify we are allowed HBM again.
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+    }
+
+    @Test
+    public void testInHBM_ThenLowLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmc.onAmbientLuxChange(1);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
+
+        // Verify we are out of HBM
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+    }
+
+    @Test
+    public void testInHBM_TestMultipleEvents_DueToAutoBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT - 0.01f);
+        advanceTime(1);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        advanceTime(2);
+
+        // Now we should be out again.
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testInHBM_TestMultipleEvents_DueToLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(true);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+
+        // Go into HBM for half the allowed window
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Move lux below threshold (ending first event);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+        // Move up some amount of time so that there's still time in the window even after a
+        // second event.
+        advanceTime((TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS) / 2);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+        // Go into HBM for just under the second half of allowed window
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 1);
+        advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Now exhaust the time
+        advanceTime(2);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    private void assertState(HighBrightnessModeController hbmc,
+            float brightnessMin, float brightnessMax, int hbmMode) {
+        assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
+        assertEquals(brightnessMax, hbmc.getCurrentBrightnessMax(), EPSILON);
+        assertEquals(hbmMode, hbmc.getHighBrightnessMode());
+    }
+
+    // Creates instance with standard initialization values.
+    private HighBrightnessModeController createDefaultHbm() {
+        return new HighBrightnessModeController(mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX,
+                DEFAULT_HBM_DATA, () -> {});
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index d784a22..8279624 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -27,17 +27,20 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.PropertyInvalidatedCache;
 import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.Process;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -61,9 +64,12 @@
 
     private DisplayDeviceRepository mDisplayDeviceRepo;
     private LogicalDisplayMapper mLogicalDisplayMapper;
-    private Context mContext;
+    private TestLooper mLooper;
+    private Handler mHandler;
 
     @Mock LogicalDisplayMapper.Listener mListenerMock;
+    @Mock Context mContextMock;
+    @Mock Resources mResourcesMock;
 
     @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
 
@@ -73,7 +79,6 @@
         System.setProperty("dexmaker.share_classloader", "true");
         MockitoAnnotations.initMocks(this);
 
-        mContext = InstrumentationRegistry.getContext();
         mDisplayDeviceRepo = new DisplayDeviceRepository(
                 new DisplayManagerService.SyncRoot(),
                 new PersistentDataStore(new PersistentDataStore.Injector() {
@@ -94,7 +99,15 @@
         // Disable binder caches in this process.
         PropertyInvalidatedCache.disableForTestMode();
 
-        mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock);
+        when(mContextMock.getResources()).thenReturn(mResourcesMock);
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_supportsConcurrentInternalDisplays))
+                .thenReturn(true);
+
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+        mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
+                mListenerMock, new DisplayManagerService.SyncRoot(), mHandler);
     }
 
 
@@ -299,7 +312,7 @@
         private DisplayDeviceInfo mSentInfo;
 
         TestDisplayDevice() {
-            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContext);
+            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
             mInfo = new DisplayDeviceInfo();
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index ef7b274..011b8f8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -138,6 +138,7 @@
         mDevicePowerStatusAction = DevicePowerStatusAction.create(mPlaybackDevice, ADDR_TV,
                 mCallbackMock);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 80da696..a29a76b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -60,6 +60,7 @@
 @RunWith(JUnit4.class)
 /** Tests for {@link HdmiCecLocalDevicePlayback} class. */
 public class HdmiCecLocalDevicePlaybackTest {
+    private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
 
     private static final int PORT_1 = 1;
     private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo(
@@ -1045,6 +1046,10 @@
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
         // 4. DUT turned off.
         mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        // TODO(b/184939731): remove waiting times once pending actions no longer block <Standby>
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
         mTestLooper.dispatchAll();
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
@@ -1502,6 +1507,7 @@
 
     @Test
     public void queryDisplayStatus() {
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
         mHdmiControlService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int result) {
@@ -1618,6 +1624,12 @@
 
     @Test
     public void shouldHandleTvPowerKey_CecDisabled() {
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+        mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+        mTestLooper.dispatchAll();
+
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                 HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
@@ -1626,6 +1638,12 @@
 
     @Test
     public void shouldHandleTvPowerKey_PowerControlModeNone() {
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+        mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+        mTestLooper.dispatchAll();
+
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
                 HdmiControlManager.POWER_CONTROL_MODE_NONE);
@@ -1633,7 +1651,22 @@
     }
 
     @Test
+    public void shouldHandleTvPowerKey_CecNotAvailable() {
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        // TV doesn't report its power status
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlService.shouldHandleTvPowerKey()).isFalse();
+    }
+
+    @Test
     public void shouldHandleTvPowerKey_CecEnabled_PowerControlModeTv() {
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+                Constants.ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON);
+        mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+        mTestLooper.dispatchAll();
+
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                 HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 950b8a2..c7a508a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -61,6 +61,7 @@
 @RunWith(JUnit4.class)
 /** Tests for {@link HdmiCecLocalDeviceTv} class. */
 public class HdmiCecLocalDeviceTvTest {
+    private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
 
     private HdmiControlService mHdmiControlService;
     private HdmiCecController mHdmiCecController;
@@ -294,6 +295,10 @@
                 HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mTestLooper.dispatchAll();
         mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        // TODO(184939731): remove waiting times once pending actions no longer block <Standby>
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
         mTestLooper.dispatchAll();
         HdmiCecMessage standby = HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_BROADCAST);
         assertThat(mNativeWrapper.getResultMessages()).contains(standby);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 5b01920..2307a85 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -392,9 +392,9 @@
     @Test
     public void isValid_giveFeatures() {
         assertMessageValidity("40:A5").isEqualTo(OK);
+        assertMessageValidity("F0:A5").isEqualTo(OK);
 
         assertMessageValidity("4F:A5").isEqualTo(ERROR_DESTINATION);
-        assertMessageValidity("F0:A5").isEqualTo(ERROR_SOURCE);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index d74bff2..4893173 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -142,6 +142,7 @@
         mPhysicalAddress = 0x2000;
         mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
     }
 
     private OneTouchPlayAction createOneTouchPlayAction(HdmiCecLocalDevicePlayback device,
@@ -161,6 +162,7 @@
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -203,6 +205,7 @@
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -245,6 +248,7 @@
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -297,6 +301,7 @@
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -342,6 +347,7 @@
         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV,
                 HdmiControlManager.POWER_STATUS_ON);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -376,6 +382,7 @@
         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV,
                 HdmiControlManager.POWER_STATUS_UNKNOWN);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
@@ -420,6 +427,7 @@
         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV,
                 HdmiControlManager.POWER_STATUS_STANDBY);
         mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
         TestCallback callback = new TestCallback();
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
index 8c92a47..0e615a6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
@@ -51,8 +51,6 @@
     private InputSensorInfo mMockInputSensorInfo;
     @Mock
     private SensorManager mMockSensorManager;
-    @Mock
-    private WindowManagerService mMockWindowManagerService;
 
     private TestableRotationResolver mFakeRotationResolverInternal;
     private com.android.server.wm.WindowOrientationListener mWindowOrientationListener;
@@ -69,7 +67,7 @@
         mFakeRotationResolverInternal = new TestableRotationResolver();
         doReturn(mMockSensorManager).when(mMockContext).getSystemService(Context.SENSOR_SERVICE);
         mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext,
-                mMockHandler, mMockWindowManagerService);
+                mMockHandler);
         mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal;
 
         mFakeSensor = new Sensor(mMockInputSensorInfo);
@@ -115,9 +113,8 @@
 
     final class TestableWindowOrientationListener extends WindowOrientationListener {
 
-        TestableWindowOrientationListener(Context context, Handler handler,
-                WindowManagerService service) {
-            super(context, handler, service);
+        TestableWindowOrientationListener(Context context, Handler handler) {
+            super(context, handler);
             this.mOrientationJudge = new OrientationSensorJudge();
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 4dbb2de..d3a825b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1244,7 +1244,6 @@
         final DisplayContent displayContent = createNewDisplay();
         Mockito.doReturn(mockLogger).when(displayContent).getMetricsLogger();
         Mockito.doReturn(oldConfig).doReturn(newConfig).when(displayContent).getConfiguration();
-        doNothing().when(displayContent).preOnConfigurationChanged();
 
         displayContent.onConfigurationChanged(newConfig);
 
@@ -1458,54 +1457,51 @@
     public void testFixedRotationWithPip() {
         final DisplayContent displayContent = mDefaultDisplay;
         unblockDisplayRotation(displayContent);
+        // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
+        doNothing().when(displayContent).prepareAppTransition(anyInt());
         // Make resume-top really update the activity state.
-        setBooted(mWm.mAtmService);
-        // Speed up the test by a few seconds.
-        mWm.mAtmService.deferWindowLayout();
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
-        final Configuration displayConfig = displayContent.getConfiguration();
-        final ActivityRecord pinnedActivity = createActivityRecord(displayContent,
-                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
-        final Task pinnedTask = pinnedActivity.getRootTask();
-        final ActivityRecord homeActivity = createActivityRecord(displayContent);
-        if (displayConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
-            homeActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
-            pinnedActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-        } else {
-            homeActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-            pinnedActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
-        }
-        final int homeConfigOrientation = homeActivity.getRequestedConfigurationOrientation();
-        final int pinnedConfigOrientation = pinnedActivity.getRequestedConfigurationOrientation();
-
-        assertEquals(homeConfigOrientation, displayConfig.orientation);
-
+        setBooted(mAtm);
         clearInvocations(mWm);
+        // Speed up the test by a few seconds.
+        mAtm.deferWindowLayout();
+
+        final ActivityRecord homeActivity = createActivityRecord(
+                displayContent.getDefaultTaskDisplayArea().getRootHomeTask());
+        final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task pinnedTask = pinnedActivity.getRootTask();
+        doReturn((displayContent.getRotation() + 1) % 4).when(displayContent)
+                .rotationForActivityInDifferentOrientation(eq(homeActivity));
+        // Enter PiP from fullscreen.
+        pinnedTask.setWindowingMode(WINDOWING_MODE_PINNED);
+
+        assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
+        assertTrue(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
+        verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+        clearInvocations(pinnedTask);
+
+        // Assume that the PiP enter animation is done then the new bounds are set. Expect the
+        // orientation update is no longer deferred.
+        displayContent.mPinnedTaskController.setEnterPipBounds(pinnedTask.getBounds());
+        // The Task Configuration was frozen to skip the change of orientation.
+        verify(pinnedTask, never()).onConfigurationChanged(any());
+        assertFalse(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
+        assertFalse(displayContent.hasTopFixedRotationLaunchingApp());
+        assertEquals(homeActivity.getConfiguration().orientation,
+                displayContent.getConfiguration().orientation);
+
+        doReturn((displayContent.getRotation() + 1) % 4).when(displayContent)
+                .rotationForActivityInDifferentOrientation(eq(pinnedActivity));
         // Leave PiP to fullscreen. Simulate the step of PipTaskOrganizer that sets the activity
         // to fullscreen, so fixed rotation will apply on it.
         pinnedActivity.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        homeActivity.setState(Task.ActivityState.STOPPED, "test");
-
         assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
-        verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-        assertNotEquals(pinnedConfigOrientation, displayConfig.orientation);
 
         // Assume the animation of PipTaskOrganizer is done and then commit fullscreen to task.
         pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         displayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
-        assertFalse(displayContent.getPinnedTaskController().isPipActiveOrWindowingModeChanging());
-        assertEquals(pinnedConfigOrientation, displayConfig.orientation);
-
-        clearInvocations(mWm);
-        // Enter PiP from fullscreen. The orientation can be updated from
-        // ensure-visibility/resume-focused-stack -> ActivityRecord#makeActiveIfNeeded -> resume.
-        pinnedTask.setWindowingMode(WINDOWING_MODE_PINNED);
-
-        assertFalse(displayContent.hasTopFixedRotationLaunchingApp());
-        verify(mWm, atLeastOnce()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-        assertEquals(homeConfigOrientation, displayConfig.orientation);
-        assertTrue(displayContent.getPinnedTaskController().isPipActiveOrWindowingModeChanging());
+        assertFalse(displayContent.mPinnedTaskController.isFreezingTaskConfig(pinnedTask));
+        assertEquals(pinnedActivity.getConfiguration().orientation,
+                displayContent.getConfiguration().orientation);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 2321a73..e1aca55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -121,8 +121,6 @@
         sMockWm = mock(WindowManagerService.class);
         sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
         sMockWm.mPolicy = mock(WindowManagerPolicy.class);
-        sMockWm.mConstants = mock(WindowManagerConstants.class);
-        sMockWm.mConstants.mRawSensorLoggingEnabled = true;
     }
 
     @AfterClass
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index bdc4b4e..2c3f52e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -91,7 +91,7 @@
         doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
 
         // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait).
-        mDisplay = (DualDisplayContent) new DualDisplayContent.Builder(mAtm, 1920, 1200).build();
+        mDisplay = new DualDisplayContent.Builder(mAtm, 1920, 1200).build();
         mFirstRoot = mDisplay.mFirstRoot;
         mSecondRoot = mDisplay.mSecondRoot;
         mFirstTda = mDisplay.getTaskDisplayArea(FEATURE_FIRST_TASK_CONTAINER);
@@ -395,7 +395,7 @@
     }
 
     /** Display with two {@link DisplayAreaGroup}. Each of them take half of the screen. */
-    private static class DualDisplayContent extends TestDisplayContent {
+    static class DualDisplayContent extends TestDisplayContent {
         final DisplayAreaGroup mFirstRoot;
         final DisplayAreaGroup mSecondRoot;
         final Rect mLastDisplayBounds;
@@ -476,11 +476,15 @@
             TestDisplayContent createInternal(Display display) {
                 return new DualDisplayContent(mService.mRootWindowContainer, display);
             }
+
+            DualDisplayContent build() {
+                return (DualDisplayContent) super.build();
+            }
         }
     }
 
     /** Policy to create a dual {@link DisplayAreaGroup} policy in test. */
-    private static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider {
+    static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider {
 
         @Override
         public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index 1b9308d..f2418c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -47,6 +47,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 // TODO(b/157888351): Move the test to inputmethod package once we find the way to test the
 //  scenario there.
@@ -59,10 +60,15 @@
 public class InputMethodMenuControllerTest extends WindowTestsBase {
 
     private InputMethodMenuController mController;
-    private TestDisplayContent mSecondaryDisplay;
+    private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay;
 
     @Before
     public void setUp() throws Exception {
+        // Let the Display to be created with the DualDisplay policy.
+        final DisplayAreaPolicy.Provider policyProvider =
+                new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
+        Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
+
         mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
 
         // Mock addWindowTokenWithOptions to create a test window token.
@@ -80,7 +86,8 @@
         }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
                 anyInt(), any());
 
-        mSecondaryDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1000).build();
+        mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
+                .Builder(mAtm, 1000, 1000).build();
 
         // Mock DisplayManagerGlobal to return test display when obtaining Display instance.
         final int displayId = mSecondaryDisplay.getDisplayId();
@@ -105,6 +112,22 @@
         assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay);
     }
 
+    @Test
+    public void testGetSettingsContextOnDualDisplayContent() {
+        final Context context = mController.getSettingsContext(mSecondaryDisplay.getDisplayId());
+
+        final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer();
+        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay);
+
+        mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
+        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
+        assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
+
+        mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
+        assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
+        assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
+    }
+
     private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {
         assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
index 212ffd5..0b1b877 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
@@ -18,14 +18,13 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static org.mockito.ArgumentMatchers.any;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
 
 import android.hardware.HardwareBuffer;
 import android.platform.test.annotations.Presubmit;
-import android.view.Surface;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
@@ -51,8 +50,8 @@
         when(mockAr.getPendingTransaction()).thenReturn(new StubTransaction());
         when(mockAr.makeChildSurface(any())).thenReturn(new MockSurfaceControlBuilder());
         when(mockAr.makeSurface()).thenReturn(new MockSurfaceControlBuilder());
-        return new WindowContainerThumbnail(new StubTransaction(), mockAr,
-                buffer, false, mock(Surface.class), mock(SurfaceAnimator.class));
+        return new WindowContainerThumbnail(new StubTransaction(), mockAr, buffer,
+                mock(SurfaceAnimator.class));
     }
 
     @Test
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 6bba65d..628f8cd 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -44,6 +44,7 @@
 import android.view.translation.TranslationContext;
 import android.view.translation.TranslationSpec;
 import android.view.translation.UiTranslationManager.UiTranslationState;
+import android.view.translation.UiTranslationSpec;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -170,6 +171,28 @@
         }
 
         @Override
+        public void registerTranslationCapabilityCallback(IRemoteCallback callback, int userId) {
+            TranslationManagerServiceImpl service;
+            synchronized (mLock) {
+                service = getServiceForUserLocked(userId);
+            }
+            if (service != null) {
+                service.registerTranslationCapabilityCallback(callback, Binder.getCallingUid());
+            }
+        }
+
+        @Override
+        public void unregisterTranslationCapabilityCallback(IRemoteCallback callback, int userId) {
+            TranslationManagerServiceImpl service;
+            synchronized (mLock) {
+                service = getServiceForUserLocked(userId);
+            }
+            if (service != null) {
+                service.unregisterTranslationCapabilityCallback(callback);
+            }
+        }
+
+        @Override
         public void onSessionCreated(TranslationContext translationContext,
                 int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
             synchronized (mLock) {
@@ -187,14 +210,14 @@
         @Override
         public void updateUiTranslationState(@UiTranslationState int state,
                 TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
-                IBinder token, int taskId, int userId) {
+                IBinder token, int taskId, UiTranslationSpec uiTranslationSpec, int userId) {
             enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
             synchronized (mLock) {
                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
                 if (service != null && (isDefaultServiceLocked(userId)
                         || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
                     service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
-                            token, taskId);
+                            token, taskId, uiTranslationSpec);
                 }
             }
         }
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index be9e0ec..4198d3b 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -41,6 +41,7 @@
 import android.view.translation.TranslationContext;
 import android.view.translation.TranslationSpec;
 import android.view.translation.UiTranslationManager.UiTranslationState;
+import android.view.translation.UiTranslationSpec;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -142,6 +143,15 @@
         }
     }
 
+    public void registerTranslationCapabilityCallback(IRemoteCallback callback, int sourceUid) {
+        mTranslationCapabilityCallbacks.register(callback, sourceUid);
+        ensureRemoteServiceLocked();
+    }
+
+    public void unregisterTranslationCapabilityCallback(IRemoteCallback callback) {
+        mTranslationCapabilityCallbacks.unregister(callback);
+    }
+
     @GuardedBy("mLock")
     void onSessionCreatedLocked(@NonNull TranslationContext translationContext, int sessionId,
             IResultReceiver resultReceiver) {
@@ -154,7 +164,7 @@
     @GuardedBy("mLock")
     public void updateUiTranslationStateLocked(@UiTranslationState int state,
             TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
-            IBinder token, int taskId) {
+            IBinder token, int taskId, UiTranslationSpec uiTranslationSpec) {
         // Get top activity for a given task id
         final ActivityTokens taskTopActivityTokens =
                 mActivityTaskManagerInternal.getTopActivityForTask(taskId);
@@ -165,6 +175,7 @@
             return;
         }
         try {
+            // TODO: Pipe uiTranslationSpec through to the UiTranslationController.
             taskTopActivityTokens.getApplicationThread().updateUiTranslationState(
                     taskTopActivityTokens.getActivityToken(), state, sourceSpec, targetSpec,
                     viewIds);
diff --git a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
index 2f6a2b0..f34567f 100644
--- a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
+++ b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
@@ -311,6 +311,11 @@
     }
 
     @Override
+    public synchronized int getAdapterState() throws RemoteException {
+        return getVendorUwbAdapter().getAdapterState();
+    }
+
+    @Override
     public synchronized void setEnabled(boolean enabled) throws RemoteException {
         getVendorUwbAdapter().setEnabled(enabled);
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index d6ed98f..3fbd40f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -65,7 +65,6 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -235,19 +234,32 @@
             Slog.d(TAG, "startListeningFromMic");
         }
 
-        AudioRecord audioRecord = createMicAudioRecord(audioFormat);
-        if (audioRecord == null) {
-            // TODO: Callback.onError();
-            return;
-        }
+        // TODO: consider making this a non-anonymous class.
+        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
+            @Override
+            public void onDetected(HotwordDetectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onDetected");
+                }
+                callback.onDetected(result, null, null);
+            }
 
-        handleSoftwareHotwordDetection(
-                audioFormat,
-                AudioReader.createFromAudioRecord(audioRecord),
-                AUDIO_SOURCE_MICROPHONE,
-                // TODO: handle bundles better.
-                new PersistableBundle(),
-                callback);
+            @Override
+            public void onRejected(HotwordRejectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onRejected");
+                }
+                // onRejected isn't allowed here
+            }
+        };
+
+        mRemoteHotwordDetectionService.run(
+                service -> service.detectFromMicrophoneSource(
+                        null,
+                        AUDIO_SOURCE_MICROPHONE,
+                        null,
+                        null,
+                        internalCallback));
     }
 
     public void startListeningFromExternalSource(
@@ -298,74 +310,12 @@
         if (DEBUG) {
             Slog.d(TAG, "detectFromDspSourceForTest");
         }
-
-        AudioRecord record = createFakeAudioRecord();
-        if (record == null) {
-            Slog.d(TAG, "Failed to create fake audio record");
-            return;
-        }
-
-        Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
-        if (clientPipe == null) {
-            Slog.d(TAG, "Failed to create pipe");
-            return;
-        }
-        ParcelFileDescriptor audioSink = clientPipe.second;
-        ParcelFileDescriptor clientRead = clientPipe.first;
-
-        record.startRecording();
-
-        mAudioCopyExecutor.execute(() -> {
-            try (OutputStream fos =
-                         new ParcelFileDescriptor.AutoCloseOutputStream(audioSink)) {
-
-                int remainToRead = 10240;
-                byte[] buffer = new byte[1024];
-                while (remainToRead > 0) {
-                    int bytesRead = record.read(buffer, 0, 1024);
-                    if (DEBUG) {
-                        Slog.d(TAG, "bytesRead = " + bytesRead);
-                    }
-                    if (bytesRead <= 0) {
-                        break;
-                    }
-                    if (bytesRead > 8) {
-                        System.arraycopy(new byte[] {'h', 'o', 't', 'w', 'o', 'r', 'd', '!'}, 0,
-                                buffer, 0, 8);
-                    }
-
-                    fos.write(buffer, 0, bytesRead);
-                    remainToRead -= bytesRead;
-                }
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed supplying audio data to validator", e);
-            }
-        });
-
-        Runnable cancellingJob = () -> {
-            Slog.d(TAG, "Timeout for getting callback from HotwordDetectionService");
-            record.stop();
-            record.release();
-            bestEffortClose(audioSink);
-            bestEffortClose(clientRead);
-        };
-
-        ScheduledFuture<?> cancelingFuture =
-                mScheduledExecutorService.schedule(
-                        cancellingJob, VALIDATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-
         IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
             @Override
             public void onDetected(HotwordDetectedResult result) throws RemoteException {
                 if (DEBUG) {
                     Slog.d(TAG, "onDetected");
                 }
-                cancelingFuture.cancel(true);
-                record.stop();
-                record.release();
-                bestEffortClose(audioSink);
-                bestEffortClose(clientRead);
-
                 externalCallback.onKeyphraseDetected(recognitionEvent);
             }
 
@@ -374,19 +324,13 @@
                 if (DEBUG) {
                     Slog.d(TAG, "onRejected");
                 }
-                cancelingFuture.cancel(true);
-                record.stop();
-                record.release();
-                bestEffortClose(audioSink);
-                bestEffortClose(clientRead);
-
                 externalCallback.onRejected(result);
             }
         };
 
         mRemoteHotwordDetectionService.run(
                 service -> service.detectFromDspSource(
-                        clientRead,
+                        recognitionEvent,
                         recognitionEvent.getCaptureFormat(),
                         VALIDATION_TIMEOUT_MILLIS,
                         internalCallback));
@@ -398,49 +342,6 @@
             Slog.d(TAG, "detectFromDspSource");
         }
 
-        AudioRecord record = createAudioRecord(recognitionEvent);
-
-        Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
-
-        if (clientPipe == null) {
-            // Error.
-            // Need to propagate as unknown error or something?
-            return;
-        }
-        ParcelFileDescriptor audioSink = clientPipe.second;
-        ParcelFileDescriptor clientRead = clientPipe.first;
-
-        record.startRecording();
-
-        mAudioCopyExecutor.execute(() -> {
-            try (OutputStream fos =
-                         new ParcelFileDescriptor.AutoCloseOutputStream(audioSink)) {
-                byte[] buffer = new byte[1024];
-
-                while (true) {
-                    int bytesRead = record.read(buffer, 0, 1024);
-
-                    if (bytesRead < 0) {
-                        break;
-                    }
-
-                    fos.write(buffer, 0, bytesRead);
-                }
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed supplying audio data to validator", e);
-            }
-        });
-
-        Runnable cancellingJob = () -> {
-            record.stop();
-            bestEffortClose(audioSink);
-            // TODO: consider calling externalCallback.onRejected(ERROR_TIMEOUT).
-        };
-
-        ScheduledFuture<?> cancelingFuture =
-                mScheduledExecutorService.schedule(
-                        cancellingJob, VALIDATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-
         // TODO: consider making this a non-anonymous class.
         IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
             @Override
@@ -448,18 +349,6 @@
                 if (DEBUG) {
                     Slog.d(TAG, "onDetected");
                 }
-                bestEffortClose(audioSink);
-                cancelingFuture.cancel(true);
-
-                // Give 2 more seconds for the interactor to start consuming the mic. If it fails to
-                // do so under the given time, we'll force-close the mic to make sure resources are
-                // freed up.
-                // TODO: consider modelling these 2 seconds in the API.
-                mScheduledExecutorService.schedule(
-                        cancellingJob,
-                        VOICE_INTERACTION_TIMEOUT_TO_OPEN_MIC_MILLIS,
-                        TimeUnit.MILLISECONDS);
-
                 // TODO: Propagate the HotwordDetectedResult.
                 externalCallback.onKeyphraseDetected(recognitionEvent);
             }
@@ -469,18 +358,16 @@
                 if (DEBUG) {
                     Slog.d(TAG, "onRejected");
                 }
-                cancelingFuture.cancel(true);
                 externalCallback.onRejected(result);
             }
         };
 
         mRemoteHotwordDetectionService.run(
                 service -> service.detectFromDspSource(
-                        clientRead,
+                        recognitionEvent,
                         recognitionEvent.getCaptureFormat(),
                         VALIDATION_TIMEOUT_MILLIS,
                         internalCallback));
-        bestEffortClose(clientRead);
     }
 
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index 9b155c9..b6f3471 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -47,4 +47,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
 include $(BUILD_PACKAGE)