Merge "[flexiglass] Wire durationScale properly for transition builders." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a42adad..ab5d503 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -532,6 +532,7 @@
     name: "android.content.pm.flags-aconfig",
     package: "android.content.pm",
     container: "system",
+    exportable: true,
     srcs: ["core/java/android/content/pm/flags.aconfig"],
 }
 
@@ -542,6 +543,18 @@
 }
 
 java_aconfig_library {
+    name: "android.content.pm.flags-aconfig-java-export",
+    aconfig_declarations: "android.content.pm.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    mode: "exported",
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
+}
+
+java_aconfig_library {
     name: "android.content.pm.flags-aconfig-java-host",
     aconfig_declarations: "android.content.pm.flags-aconfig",
     host_supported: true,
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index f56a950..28efa1e 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -1144,31 +1144,47 @@
     ],
 }
 
-droidstubs {
-    name: "api_versions_public",
-    srcs: [":android_stubs_current_with_test_libs{.jar}"],
+// Defaults for `droidstubs` modules that generate `api-versions.xml` files for
+// the various API surfaces.
+stubs_defaults {
+    name: "api_versions_base_defaults",
+    defaults_visibility: ["//visibility:private"],
     generate_stubs: false,
     api_levels_annotations_enabled: true,
     api_levels_annotations_dirs: [
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    api_levels_sdk_type: "public",
+}
+
+// Defaults for `droidstubs` modules that generate complete `api-versions.xml`
+// files, i.e. include SDK extensions.
+stubs_defaults {
+    name: "api_versions_complete_defaults",
+    defaults_visibility: ["//visibility:private"],
+    defaults: ["api_versions_base_defaults"],
     extensions_info_file: ":sdk-extensions-info",
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the public APIs, both updatable and non-updatable and historic
+// information about all previous dessert and SDK extension releases.
+droidstubs {
+    name: "api_versions_public",
+    defaults: ["api_versions_complete_defaults"],
+    srcs: [":android_stubs_current_with_test_libs{.jar}"],
+    api_levels_sdk_type: "public",
     visibility: ["//frameworks/base"],
 }
 
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about all the system APIs, both updatable and non-updatable and historic
+// information about all previous dessert and SDK extension releases.
 droidstubs {
     name: "api_versions_system",
+    defaults: ["api_versions_complete_defaults"],
     srcs: [":android_system_stubs_current_with_test_libs{.jar}"],
-    generate_stubs: false,
-    api_levels_annotations_enabled: true,
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
     api_levels_sdk_type: "system",
-    extensions_info_file: ":sdk-extensions-info",
     dists: [
         // Make the api-versions.xml file for the system API available in the
         // sdk build target.
@@ -1180,42 +1196,43 @@
     ],
 }
 
-// This module can be built with:
-// m out/soong/.intermediates/frameworks/base/api/api_versions_module_lib/android_common/metalava/api-versions.xml
-droidstubs {
-    name: "api_versions_module_lib",
-    srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
-    generate_stubs: false,
-    api_levels_annotations_enabled: true,
+// Defaults for `droidstubs` modules that generate `api-versions.xml` files that
+// only include non-updatable code, i.e. for platform API only, not SDK
+// extensions.
+stubs_defaults {
+    name: "api_versions_non_updatable_defaults",
+    defaults_visibility: ["//visibility:private"],
+    defaults: ["api_versions_base_defaults"],
     // this only has the non-updatable portions of the module lib sdk,
     // which can reference classes from updatable apexes, so remove references to them
     // from this api_versions file.
     flags: ["--remove-missing-class-references-in-api-levels"],
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
-    api_levels_sdk_type: "module-lib",
     // extensions_info_file is purposefully omitted, because this module should just be
     // the non-updatable portions of the sdk, and extension sdks are updatable.
 }
 
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about only the non-updatable module-lib APIs and historic information about
+// all previous dessert and SDK extension releases. That historic information
+// may include information about APIs that were previously not-updatable which
+// have since become updatable.
+droidstubs {
+    name: "api_versions_module_lib",
+    defaults: ["api_versions_non_updatable_defaults"],
+    srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
+    api_levels_sdk_type: "module-lib",
+}
+
+// Produces an `api-versions.xml` file that includes up-to-date information
+// about only the non-updatable system-server APIs and historic information
+// about all previous dessert and SDK extension releases. That historic
+// information may include information about APIs that were previously
+// not-updatable which have since become updatable.
 droidstubs {
     name: "api_versions_system_server",
+    defaults: ["api_versions_non_updatable_defaults"],
     srcs: [":android_system_server_stubs_current_with_test_libs{.jar}"],
-    generate_stubs: false,
-    api_levels_annotations_enabled: true,
-    // this only has the non-updatable portions of the system server sdk,
-    // which can reference classes from updatable apexes, so remove references to them
-    // from this api_versions file.
-    flags: ["--remove-missing-class-references-in-api-levels"],
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
     api_levels_sdk_type: "system-server",
-    // extensions_info_file is purposefully omitted, because this module should just be
-    // the non-updatable portions of the sdk, and extension sdks are updatable.
 }
 
 /////////////////////////////////////////////////////////////////////
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0372b7b..2437be8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1529,9 +1529,9 @@
 package android.hardware {
 
   @Deprecated public class Camera {
-    method @Deprecated public static void getCameraInfo(int, @NonNull android.content.Context, boolean, android.hardware.Camera.CameraInfo);
+    method @Deprecated public static void getCameraInfo(int, @NonNull android.content.Context, int, android.hardware.Camera.CameraInfo);
     method @Deprecated public static int getNumberOfCameras(@NonNull android.content.Context);
-    method @Deprecated public static android.hardware.Camera open(int, @NonNull android.content.Context, boolean);
+    method @Deprecated public static android.hardware.Camera open(int, @NonNull android.content.Context, int);
     method @Deprecated public final void setPreviewSurface(android.view.Surface) throws java.io.IOException;
   }
 
@@ -1606,11 +1606,15 @@
   public final class CameraManager {
     method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
     method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
+    method @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static int getRotationOverrideInternal(@Nullable android.content.Context, @Nullable android.content.pm.PackageManager, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, boolean, @Nullable android.os.Handler, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
     method public static boolean shouldOverrideToPortrait(@Nullable android.content.pm.PackageManager, @Nullable String);
     field public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait";
     field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L
+    field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = 1; // 0x1
+    field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_ROTATION_ONLY = 2; // 0x2
   }
 
   public abstract static class CameraManager.AvailabilityCallback {
@@ -3989,15 +3993,15 @@
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void finishTrackingPendingImeVisibilityRequests();
     method public int getDisplayId();
-    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
-    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void hideSoftInputFromServerForTest();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
-    method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8171723..9437c74 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -300,6 +300,16 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     static final long ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN = 330902016;
 
+    /**
+     * The corresponding vendor API for Android V
+     *
+     * <p>Starting with Android V, the vendor API format has switched to YYYYMM.
+     *
+     * @see <a href="https://preview.source.android.com/docs/core/architecture/api-flags">Vendor API
+     *     level</a>
+     */
+    private static final int VENDOR_API_FOR_ANDROID_V = 202404;
+
     // Service registry information.
     // This information is never changed once static initialization has completed.
     private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
@@ -465,9 +475,10 @@
                 new CachedServiceFetcher<VcnManager>() {
             @Override
             public VcnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                if (shouldCheckTelephonyFeatures()
-                    && !ctx.getPackageManager().hasSystemFeature(
-                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
+                final String telephonyFeatureToCheck = getVcnFeatureDependency();
+
+                if (telephonyFeatureToCheck != null
+                    && !ctx.getPackageManager().hasSystemFeature(telephonyFeatureToCheck)) {
                     return null;
                 }
 
@@ -1768,16 +1779,24 @@
     // partition SDK level, not application's target SDK version (which BTW we
     // also check through Compatibility framework a few lines below).
     @SuppressWarnings("AndroidFrameworkCompatChange")
-    private static boolean shouldCheckTelephonyFeatures() {
+    @Nullable
+    private static String getVcnFeatureDependency() {
+        // Check SDK version of the client app. Apps targeting pre-V SDK might
+        // have not checked for existence of these features.
+        if (!Compatibility.isChangeEnabled(ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN)) {
+            return null;
+        }
+
         // Check SDK version of the vendor partition. Pre-V devices might have
         // incorrectly under-declared telephony features.
         final int vendorApiLevel = SystemProperties.getInt(
                 "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
-        if (vendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) return false;
+        if (vendorApiLevel < VENDOR_API_FOR_ANDROID_V) {
+            return PackageManager.FEATURE_TELEPHONY;
+        } else {
+            return PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION;
+        }
 
-        // Check SDK version of the client app. Apps targeting pre-V SDK might
-        // have not checked for existence of these features.
-        return Compatibility.isChangeEnabled(ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN);
     }
 
     /**
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index e5f2976..23b2ea4 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
 import android.annotation.SystemService;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -40,6 +41,15 @@
 @SystemService(Context.TRUST_SERVICE)
 public class TrustManager {
 
+    /**
+     * Intent action used to identify services that can serve as significant providers.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String ACTION_BIND_SIGNIFICANT_PLACE_PROVIDER =
+            "com.android.trust.provider.SignificantPlaceProvider.BIND";
+
     private static final int MSG_TRUST_CHANGED = 1;
     private static final int MSG_TRUST_MANAGED_CHANGED = 2;
     private static final int MSG_TRUST_ERROR = 3;
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 2dced96..72f992a 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -35,6 +35,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.SparseArray;
 import android.widget.RemoteViews;
 import android.widget.RemoteViews.InteractionHandler;
@@ -52,12 +53,15 @@
  */
 public class AppWidgetHost {
 
+    private static final String TAG = "AppWidgetHost";
+
     static final int HANDLE_UPDATE = 1;
     static final int HANDLE_PROVIDER_CHANGED = 2;
     static final int HANDLE_PROVIDERS_CHANGED = 3;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     static final int HANDLE_VIEW_DATA_CHANGED = 4;
     static final int HANDLE_APP_WIDGET_REMOVED = 5;
+    static final int HANDLE_VIEW_UPDATE_DEFERRED = 6;
 
     final static Object sServiceLock = new Object();
     @UnsupportedAppUsage
@@ -131,6 +135,15 @@
             msg.sendToTarget();
         }
 
+        public void updateAppWidgetDeferred(int appWidgetId) {
+            Handler handler = mWeakHandler.get();
+            if (handler == null) {
+                return;
+            }
+            Message msg = handler.obtainMessage(HANDLE_VIEW_UPDATE_DEFERRED, appWidgetId, 0, null);
+            msg.sendToTarget();
+        }
+
         private static boolean isLocalBinder() {
             return Process.myPid() == Binder.getCallingPid();
         }
@@ -163,6 +176,10 @@
                     viewDataChanged(msg.arg1, msg.arg2);
                     break;
                 }
+                case HANDLE_VIEW_UPDATE_DEFERRED: {
+                    updateAppWidgetDeferred(msg.arg1);
+                    break;
+                }
             }
         }
     }
@@ -480,14 +497,32 @@
         void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget);
 
         /**
-         * This function is called when the RemoteViews of the app widget is updated
-         * @param views The new RemoteViews to be set for the app widget
+         * This function is called when the {@code RemoteViews} of the app widget is updated
+         * @param views The new {@code RemoteViews} to be set for the app widget
          *
          * @hide
          */
         void updateAppWidget(@Nullable RemoteViews views);
 
         /**
+         * Called for the listener to handle deferred {@code RemoteViews} updates. Default
+         * implementation is to update the widget directly.
+         * @param packageName The package name used for uid verification on the service side
+         * @param appWidgetId The widget id of the listener
+         *
+         * @hide
+         */
+        default void updateAppWidgetDeferred(String packageName, int appWidgetId) {
+            RemoteViews latestViews = null;
+            try {
+                latestViews = sService.getAppWidgetViews(packageName, appWidgetId);
+            } catch (Exception e) {
+                Log.e(TAG, "updateAppWidgetDeferred: ", e);
+            }
+            updateAppWidget(latestViews);
+        }
+
+        /**
          * This function is called when the view ID is changed for the app widget
          * @param viewId The new view ID to be be set for the widget
          *
@@ -563,6 +598,15 @@
         }
     }
 
+    private void updateAppWidgetDeferred(int appWidgetId) {
+        AppWidgetHostListener v = getListener(appWidgetId);
+        if (v == null) {
+            Log.e(TAG, "updateAppWidgetDeferred: null listener for id: " + appWidgetId);
+            return;
+        }
+        v.updateAppWidgetDeferred(mContextOpPackageName, appWidgetId);
+    }
+
     /**
      * Clear the list of Views that have been created by this AppWidgetHost.
      */
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 3486d5e..afddc77 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -419,6 +419,10 @@
             if (available <= 0) {
                 return -1;
             }
+            if (count == 0) {
+                // Java's InputStream explicitly specifies that this returns zero.
+                return 0;
+            }
 
             if (count > available) count = available;
             try {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index a9f70c9..32d2a6f 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -308,8 +308,8 @@
      */
     public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
         Context context = ActivityThread.currentApplication().getApplicationContext();
-        boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(context);
-        getCameraInfo(cameraId, context, overrideToPortrait, cameraInfo);
+        final int rotationOverride = CameraManager.getRotationOverride(context);
+        getCameraInfo(cameraId, context, rotationOverride, cameraInfo);
     }
 
     /**
@@ -320,8 +320,8 @@
     @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
     @TestApi
     public static void getCameraInfo(int cameraId, @NonNull Context context,
-            boolean overrideToPortrait, CameraInfo cameraInfo) {
-        _getCameraInfo(cameraId, overrideToPortrait, context.getDeviceId(),
+            int rotationOverride, CameraInfo cameraInfo) {
+        _getCameraInfo(cameraId, rotationOverride, context.getDeviceId(),
                 getDevicePolicyFromContext(context), cameraInfo);
         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
         IAudioService audioService = IAudioService.Stub.asInterface(b);
@@ -336,7 +336,7 @@
         }
     }
 
-    private native static void _getCameraInfo(int cameraId, boolean overrideToPortrait,
+    private native static void _getCameraInfo(int cameraId, int rotationOverride,
             int deviceId, int devicePolicy, CameraInfo cameraInfo);
 
     private static int getDevicePolicyFromContext(Context context) {
@@ -441,8 +441,8 @@
      */
     public static Camera open(int cameraId) {
         Context context = ActivityThread.currentApplication().getApplicationContext();
-        boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(context);
-        return open(cameraId, context, overrideToPortrait);
+        final int rotationOverride = CameraManager.getRotationOverride(context);
+        return open(cameraId, context, rotationOverride);
     }
 
     /**
@@ -452,8 +452,8 @@
      */
     @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
     @TestApi
-    public static Camera open(int cameraId, @NonNull Context context, boolean overrideToPortrait) {
-        return new Camera(cameraId, context, overrideToPortrait);
+    public static Camera open(int cameraId, @NonNull Context context, int rotationOverride) {
+        return new Camera(cameraId, context, rotationOverride);
     }
 
     /**
@@ -524,7 +524,7 @@
         return open(cameraId);
     }
 
-    private int cameraInit(int cameraId, Context context, boolean overrideToPortrait) {
+    private int cameraInit(int cameraId, Context context, int rotationOverride) {
         mShutterCallback = null;
         mRawImageCallback = null;
         mJpegCallback = null;
@@ -544,7 +544,7 @@
 
         boolean forceSlowJpegMode = shouldForceSlowJpegMode();
         return native_setup(new WeakReference<>(this), cameraId,
-                ActivityThread.currentOpPackageName(), overrideToPortrait, forceSlowJpegMode,
+                ActivityThread.currentOpPackageName(), rotationOverride, forceSlowJpegMode,
                 context.getDeviceId(), getDevicePolicyFromContext(context));
     }
 
@@ -562,9 +562,9 @@
     }
 
     /** used by Camera#open, Camera#open(int) */
-    Camera(int cameraId, @NonNull Context context, boolean overrideToPortrait) {
+    Camera(int cameraId, @NonNull Context context, int rotationOverride) {
         Objects.requireNonNull(context);
-        int err = cameraInit(cameraId, context, overrideToPortrait);
+        final int err = cameraInit(cameraId, context, rotationOverride);
         if (checkInitErrors(err)) {
             if (err == -EACCES) {
                 throw new RuntimeException("Fail to connect to camera service");
@@ -629,7 +629,7 @@
 
     @UnsupportedAppUsage
     private native int native_setup(Object cameraThis, int cameraId, String packageName,
-            boolean overrideToPortrait, boolean forceSlowJpegMode, int deviceId, int devicePolicy);
+            int rotationOverride, boolean forceSlowJpegMode, int deviceId, int devicePolicy);
 
     private native final void native_release();
 
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 90a2cf0..a019612 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -28,6 +28,8 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
 import android.app.compat.CompatChanges;
 import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.ChangeId;
@@ -67,6 +69,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Size;
 import android.view.Display;
 
@@ -169,6 +172,36 @@
             "camera.enable_landscape_to_portrait";
 
     /**
+     * Does not override landscape feed to portrait.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public static final int ROTATION_OVERRIDE_NONE = ICameraService.ROTATION_OVERRIDE_NONE;
+
+    /**
+     * Crops and rotates landscape camera feed to portrait, and changes sensor orientation to
+     * portrait.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT =
+            ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT;
+
+    /**
+     * Crops and rotates landscape camera feed to portrait, but doesn't change sensor orientation.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    public static final int ROTATION_OVERRIDE_ROTATION_ONLY =
+            ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY;
+
+    /**
      * Enable physical camera availability callbacks when the logical camera is unavailable
      *
      * <p>Previously once a logical camera becomes unavailable, no
@@ -347,7 +380,8 @@
      */
     @NonNull
     public Set<Set<String>> getConcurrentCameraIds() throws CameraAccessException {
-        return CameraManagerGlobal.get().getConcurrentCameraIds();
+        return CameraManagerGlobal.get().getConcurrentCameraIds(mContext.getDeviceId(),
+                getDevicePolicyFromContext(mContext));
     }
 
     /**
@@ -386,7 +420,8 @@
             @NonNull Map<String, SessionConfiguration> cameraIdAndSessionConfig)
             throws CameraAccessException {
         return CameraManagerGlobal.get().isConcurrentSessionConfigurationSupported(
-                cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion);
+                cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion,
+                mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
     }
 
     /**
@@ -627,7 +662,8 @@
                 CameraMetadataNative physicalCameraInfo =
                         cameraService.getCameraCharacteristics(physicalCameraId,
                                 mContext.getApplicationInfo().targetSdkVersion,
-                                /*overrideToPortrait*/ false, DEVICE_ID_DEFAULT,
+                                /*rotationOverride*/ ICameraService.ROTATION_OVERRIDE_NONE,
+                                DEVICE_ID_DEFAULT,
                                 DEVICE_POLICY_DEFAULT);
                 StreamConfiguration[] configs = physicalCameraInfo.get(
                         CameraCharacteristics.
@@ -674,7 +710,7 @@
     @NonNull
     public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
             throws CameraAccessException {
-        return getCameraCharacteristics(cameraId, shouldOverrideToPortrait(mContext));
+        return getCameraCharacteristics(cameraId, getRotationOverride(mContext));
     }
 
     /**
@@ -699,7 +735,16 @@
     @NonNull
     public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId,
             boolean overrideToPortrait) throws CameraAccessException {
-        CameraCharacteristics characteristics;
+        return getCameraCharacteristics(cameraId,
+                overrideToPortrait
+                        ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+                        : ICameraService.ROTATION_OVERRIDE_NONE);
+    }
+
+    @NonNull
+    private CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId,
+            int rotationOverride) throws CameraAccessException {
+        CameraCharacteristics characteristics = null;
         if (CameraManagerGlobal.sCameraServiceDisabled) {
             throw new IllegalArgumentException("No cameras available on device");
         }
@@ -711,7 +756,7 @@
             }
             try {
                 CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
-                        mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait,
+                        mContext.getApplicationInfo().targetSdkVersion, rotationOverride,
                         mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
                 characteristics = prepareCameraCharacteristics(cameraId, info, cameraService);
             } catch (ServiceSpecificException e) {
@@ -752,7 +797,7 @@
 
             boolean hasConcurrentStreams =
                     CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId,
-                            mContext.getDeviceId());
+                            mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
             metadata.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
 
             Size displaySize = getDisplaySize();
@@ -926,7 +971,7 @@
      */
     private CameraDevice openCameraDeviceUserAsync(String cameraId,
             CameraDevice.StateCallback callback, Executor executor, final int uid,
-            final int oomScoreOffset, boolean overrideToPortrait) throws CameraAccessException {
+            final int oomScoreOffset, int rotationOverride) throws CameraAccessException {
         CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
         CameraDevice device = null;
         Map<String, CameraCharacteristics> physicalIdsToChars =
@@ -961,7 +1006,7 @@
                 cameraUser = cameraService.connectDevice(callbacks, cameraId,
                     mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
                     oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
-                    overrideToPortrait, mContext.getDeviceId(),
+                        rotationOverride, mContext.getDeviceId(),
                         getDevicePolicyFromContext(mContext));
             } catch (ServiceSpecificException e) {
                 if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
@@ -1126,7 +1171,10 @@
             @Nullable Handler handler,
             @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException {
         openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
-                         USE_CALLING_UID, /*oomScoreOffset*/0, overrideToPortrait);
+                         USE_CALLING_UID, /*oomScoreOffset*/0,
+                         overrideToPortrait
+                                 ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+                                 : ICameraService.ROTATION_OVERRIDE_NONE);
     }
 
     /**
@@ -1240,7 +1288,7 @@
                     "oomScoreOffset < 0, cannot increase priority of camera client");
         }
         openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset,
-                shouldOverrideToPortrait(mContext));
+                getRotationOverride(mContext));
     }
 
     /**
@@ -1258,11 +1306,14 @@
      *             Must be USE_CALLING_UID unless the caller is a trusted service.
      * @param oomScoreOffset
      *             The minimum oom score that cameraservice must see for this client.
+     * @param rotationOverride
+     *             The type of rotation override (none, override_to_portrait, rotation_only)
+     *             that should be followed for this camera id connection
      * @hide
      */
     public void openCameraForUid(@NonNull String cameraId,
             @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
-            int clientUid, int oomScoreOffset, boolean overrideToPortrait)
+            int clientUid, int oomScoreOffset, int rotationOverride)
             throws CameraAccessException {
 
         if (cameraId == null) {
@@ -1275,7 +1326,7 @@
         }
 
         openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset,
-                overrideToPortrait);
+                rotationOverride);
     }
 
     /**
@@ -1297,7 +1348,7 @@
             @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
             int clientUid) throws CameraAccessException {
         openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0,
-                shouldOverrideToPortrait(mContext));
+                getRotationOverride(mContext));
     }
 
     /**
@@ -1442,7 +1493,7 @@
     /**
      * @hide
      */
-    public static boolean shouldOverrideToPortrait(@Nullable Context context) {
+    public static int getRotationOverride(@Nullable Context context) {
         PackageManager packageManager = null;
         String packageName = null;
 
@@ -1451,7 +1502,64 @@
             packageName = context.getOpPackageName();
         }
 
-        return shouldOverrideToPortrait(packageManager, packageName);
+        return getRotationOverride(context, packageManager, packageName);
+    }
+
+    /**
+     * @hide
+     */
+    public static int getRotationOverride(@Nullable Context context,
+            @Nullable PackageManager packageManager, @Nullable String packageName) {
+        if (com.android.window.flags.Flags.cameraCompatForFreeform()) {
+            return getRotationOverrideInternal(context, packageManager, packageName);
+        } else {
+            return shouldOverrideToPortrait(packageManager, packageName)
+                        ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+                        : ICameraService.ROTATION_OVERRIDE_NONE;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+    @TestApi
+    public static int getRotationOverrideInternal(@Nullable Context context,
+            @Nullable PackageManager packageManager, @Nullable String packageName) {
+        if (!CameraManagerGlobal.sLandscapeToPortrait) {
+            return ICameraService.ROTATION_OVERRIDE_NONE;
+        }
+
+        if (context != null) {
+            final ActivityManager activityManager =
+                    context.getSystemService(ActivityManager.class);
+            for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) {
+                final TaskInfo taskInfo = appTask.getTaskInfo();
+                if (taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode
+                        != 0
+                        && taskInfo.topActivity != null
+                        && taskInfo.topActivity.getPackageName().equals(packageName)) {
+                    // WindowManager has requested rotation override.
+                    return ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY;
+                }
+            }
+        }
+
+        if (packageManager != null && packageName != null) {
+            try {
+                return packageManager.getProperty(
+                        PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
+                        packageName).getBoolean()
+                        ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+                        : ICameraService.ROTATION_OVERRIDE_NONE;
+            } catch (PackageManager.NameNotFoundException e) {
+                // No such property
+            }
+        }
+
+        return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT)
+                ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
+                : ICameraService.ROTATION_OVERRIDE_NONE;
     }
 
     /**
@@ -1459,7 +1567,7 @@
      */
     @TestApi
     public static boolean shouldOverrideToPortrait(@Nullable PackageManager packageManager,
-                                                   @Nullable String packageName) {
+            @Nullable String packageName) {
         if (!CameraManagerGlobal.sLandscapeToPortrait) {
             return false;
         }
@@ -1477,6 +1585,7 @@
         return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT);
     }
 
+
     /**
      * @hide
      */
@@ -1991,7 +2100,7 @@
         // Opened Camera ID -> apk name map
         private final ArrayMap<DeviceCameraInfo, String> mOpenedDevices = new ArrayMap<>();
 
-        private final Set<Set<String>> mConcurrentCameraIdCombinations = new ArraySet<>();
+        private final Set<Set<DeviceCameraInfo>> mConcurrentCameraIdCombinations = new ArraySet<>();
 
         // Registered availability callbacks and their executors
         private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap = new ArrayMap<>();
@@ -2150,7 +2259,13 @@
                 ConcurrentCameraIdCombination[] cameraIdCombinations =
                         cameraService.getConcurrentCameraIds();
                 for (ConcurrentCameraIdCombination comb : cameraIdCombinations) {
-                    mConcurrentCameraIdCombinations.add(comb.getConcurrentCameraIdCombination());
+                    Set<Pair<String, Integer>> combination =
+                            comb.getConcurrentCameraIdCombination();
+                    Set<DeviceCameraInfo> deviceCameraInfoSet = new ArraySet<>();
+                    for (Pair<String, Integer> entry : combination) {
+                        deviceCameraInfoSet.add(new DeviceCameraInfo(entry.first, entry.second));
+                    }
+                    mConcurrentCameraIdCombinations.add(deviceCameraInfoSet);
                 }
             } catch (ServiceSpecificException e) {
                 // Unexpected failure
@@ -2235,13 +2350,12 @@
             return cameraIds.toArray(new String[0]);
         }
 
-        private Set<Set<String>> extractConcurrentCameraIdListLocked() {
+        private Set<Set<String>> extractConcurrentCameraIdListLocked(int deviceId,
+                int devicePolicy) {
             Set<Set<String>> concurrentCameraIds = new ArraySet<>();
-            for (Set<String> cameraIds : mConcurrentCameraIdCombinations) {
+            for (Set<DeviceCameraInfo> deviceCameraInfos : mConcurrentCameraIdCombinations) {
                 Set<String> extractedCameraIds = new ArraySet<>();
-                for (String cameraId : cameraIds) {
-                    // TODO(b/291736219): This to be made device-aware.
-                    DeviceCameraInfo info = new DeviceCameraInfo(cameraId, DEVICE_ID_DEFAULT);
+                for (DeviceCameraInfo info : deviceCameraInfos) {
                     // if the camera id status is NOT_PRESENT or ENUMERATING; skip the device.
                     // TODO: Would a device status NOT_PRESENT ever be in the map ? it gets removed
                     // in the callback anyway.
@@ -2254,9 +2368,14 @@
                             || status == ICameraServiceListener.STATUS_NOT_PRESENT) {
                         continue;
                     }
-                    extractedCameraIds.add(cameraId);
+                    if (shouldHideCamera(deviceId, devicePolicy, info)) {
+                        continue;
+                    }
+                    extractedCameraIds.add(info.mCameraId);
                 }
-                concurrentCameraIds.add(extractedCameraIds);
+                if (!extractedCameraIds.isEmpty()) {
+                    concurrentCameraIds.add(extractedCameraIds);
+                }
             }
             return concurrentCameraIds;
         }
@@ -2417,12 +2536,13 @@
             return cameraIds;
         }
 
-        public @NonNull Set<Set<String>> getConcurrentCameraIds() {
+        public @NonNull Set<Set<String>> getConcurrentCameraIds(int deviceId, int devicePolicy) {
             Set<Set<String>> concurrentStreamingCameraIds;
             synchronized (mLock) {
                 // Try to make sure we have an up-to-date list of concurrent camera devices.
                 connectCameraServiceLocked();
-                concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked();
+                concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked(deviceId,
+                        devicePolicy);
             }
             // TODO: Some sort of sorting  ?
             return concurrentStreamingCameraIds;
@@ -2430,13 +2550,12 @@
 
         public boolean isConcurrentSessionConfigurationSupported(
                 @NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations,
-                int targetSdkVersion) throws CameraAccessException {
+                int targetSdkVersion, int deviceId, int devicePolicy)
+                throws CameraAccessException {
             if (cameraIdsAndSessionConfigurations == null) {
                 throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null");
             }
 
-            // TODO(b/291736219): Check if this API needs to be made device-aware.
-
             int size = cameraIdsAndSessionConfigurations.size();
             if (size == 0) {
                 throw new IllegalArgumentException("camera id and session combination is empty");
@@ -2446,14 +2565,20 @@
                 // Go through all the elements and check if the camera ids are valid at least /
                 // belong to one of the combinations returned by getConcurrentCameraIds()
                 boolean subsetFound = false;
-                for (Set<String> combination : mConcurrentCameraIdCombinations) {
-                    if (combination.containsAll(cameraIdsAndSessionConfigurations.keySet())) {
+                for (Set<DeviceCameraInfo> combination : mConcurrentCameraIdCombinations) {
+                    Set<DeviceCameraInfo> infos = new ArraySet<>();
+                    for (String cameraId : cameraIdsAndSessionConfigurations.keySet()) {
+                        infos.add(new DeviceCameraInfo(cameraId,
+                                devicePolicy == DEVICE_POLICY_DEFAULT
+                                        ? DEVICE_ID_DEFAULT : deviceId));
+                    }
+                    if (combination.containsAll(infos)) {
                         subsetFound = true;
                     }
                 }
                 if (!subsetFound) {
                     Log.v(TAG, "isConcurrentSessionConfigurationSupported called with a subset of"
-                            + "camera ids not returned by getConcurrentCameraIds");
+                            + " camera ids not returned by getConcurrentCameraIds");
                     return false;
                 }
                 CameraIdAndSessionConfiguration [] cameraIdsAndConfigs =
@@ -2467,7 +2592,7 @@
                 }
                 try {
                     return mCameraService.isConcurrentSessionConfigurationSupported(
-                            cameraIdsAndConfigs, targetSdkVersion);
+                            cameraIdsAndConfigs, targetSdkVersion, deviceId, devicePolicy);
                 } catch (ServiceSpecificException e) {
                     throw ExceptionUtils.throwAsPublicException(e);
                 } catch (RemoteException e) {
@@ -2486,8 +2611,10 @@
          * @return Whether the camera device was found in the set of combinations returned by
          *         getConcurrentCameraIds
          */
-        public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId) {
-            DeviceCameraInfo info = new DeviceCameraInfo(cameraId, deviceId);
+        public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId,
+                int devicePolicy) {
+            DeviceCameraInfo info = new DeviceCameraInfo(cameraId,
+                    devicePolicy == DEVICE_POLICY_DEFAULT ? DEVICE_ID_DEFAULT : deviceId);
             if (!mDeviceStatus.containsKey(info)) {
                 // physical camera ids aren't advertised in concurrent camera id combinations.
                 if (DEBUG) {
@@ -2496,8 +2623,8 @@
                 }
                 return false;
             }
-            for (Set<String> comb : mConcurrentCameraIdCombinations) {
-                if (comb.contains(cameraId)) {
+            for (Set<DeviceCameraInfo> comb : mConcurrentCameraIdCombinations) {
+                if (comb.contains(info)) {
                     return true;
                 }
             }
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 372839d..8898a4c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -134,7 +134,8 @@
             try {
                 CameraMetadataNative metadata = cameraService.getSessionCharacteristics(
                         mCameraId, mTargetSdkVersion,
-                        CameraManager.shouldOverrideToPortrait(mContext), sessionConfig,
+                        CameraManager.getRotationOverride(mContext),
+                        sessionConfig,
                         mContext.getDeviceId(),
                         mCameraManager.getDevicePolicyFromContext(mContext));
 
diff --git a/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java b/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java
index 8f4d636..1a8bf1d 100644
--- a/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java
+++ b/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java
@@ -13,13 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.hardware.camera2.utils;
 
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Pair;
 
-import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -30,21 +32,21 @@
  */
 public class ConcurrentCameraIdCombination implements Parcelable {
 
-    private Set<String> mConcurrentCameraIds = new HashSet<>();
+    private final Set<Pair<String, Integer>> mConcurrentCameraIdDeviceIdPairs = new ArraySet<>();
 
     public static final @NonNull
             Parcelable.Creator<ConcurrentCameraIdCombination> CREATOR =
-            new Parcelable.Creator<ConcurrentCameraIdCombination>() {
-        @Override
-        public ConcurrentCameraIdCombination createFromParcel(Parcel in) {
-            return new ConcurrentCameraIdCombination(in);
-        }
+            new Parcelable.Creator<>() {
+                @Override
+                public ConcurrentCameraIdCombination createFromParcel(Parcel in) {
+                    return new ConcurrentCameraIdCombination(in);
+                }
 
-        @Override
-        public ConcurrentCameraIdCombination[] newArray(int size) {
-            return new ConcurrentCameraIdCombination[size];
-        }
-    };
+                @Override
+                public ConcurrentCameraIdCombination[] newArray(int size) {
+                    return new ConcurrentCameraIdCombination[size];
+                }
+            };
 
     private ConcurrentCameraIdCombination(Parcel in) {
         readFromParcel(in);
@@ -57,9 +59,10 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mConcurrentCameraIds.size());
-        for (String cameraId : mConcurrentCameraIds) {
-            dest.writeString(cameraId);
+        dest.writeInt(mConcurrentCameraIdDeviceIdPairs.size());
+        for (Pair<String, Integer> cameraIdDeviceIdPair : mConcurrentCameraIdDeviceIdPairs) {
+            dest.writeString(cameraIdDeviceIdPair.first);
+            dest.writeInt(cameraIdDeviceIdPair.second);
         }
     }
 
@@ -67,7 +70,7 @@
      * helper for CREATOR
      */
     public void readFromParcel(Parcel in) {
-        mConcurrentCameraIds.clear();
+        mConcurrentCameraIdDeviceIdPairs.clear();
         int cameraCombinationSize = in.readInt();
         if (cameraCombinationSize < 0) {
             throw new RuntimeException("cameraCombinationSize " + cameraCombinationSize
@@ -78,14 +81,15 @@
             if (cameraId == null) {
                 throw new RuntimeException("Failed to read camera id from Parcel");
             }
-            mConcurrentCameraIds.add(cameraId);
+            int deviceId = in.readInt();
+            mConcurrentCameraIdDeviceIdPairs.add(new Pair<>(cameraId, deviceId));
         }
     }
 
     /**
      * Get this concurrent camera id combination.
      */
-    public Set<String> getConcurrentCameraIdCombination() {
-        return mConcurrentCameraIds;
+    public Set<Pair<String, Integer>> getConcurrentCameraIdCombination() {
+        return mConcurrentCameraIdDeviceIdPairs;
     }
 }
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 6952e5d..f7a22f5 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -3,3 +3,4 @@
 include /services/core/java/com/android/server/display/OWNERS
 
 quxiangfang@google.com
+donpaul@google.com
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ISignificantPlaceProvider.aidl b/core/java/android/hardware/location/ISignificantPlaceProvider.aidl
new file mode 100644
index 0000000..e02169e
--- /dev/null
+++ b/core/java/android/hardware/location/ISignificantPlaceProvider.aidl
@@ -0,0 +1,10 @@
+package android.hardware.location;
+
+import android.hardware.location.ISignificantPlaceProviderManager;
+
+/**
+ * @hide
+ */
+oneway interface ISignificantPlaceProvider {
+    void setSignificantPlaceProviderManager(in ISignificantPlaceProviderManager manager);
+}
diff --git a/core/java/android/hardware/location/ISignificantPlaceProviderManager.aidl b/core/java/android/hardware/location/ISignificantPlaceProviderManager.aidl
new file mode 100644
index 0000000..76eefe7
--- /dev/null
+++ b/core/java/android/hardware/location/ISignificantPlaceProviderManager.aidl
@@ -0,0 +1,8 @@
+package android.hardware.location;
+
+/**
+ * @hide
+ */
+interface ISignificantPlaceProviderManager {
+    void setInSignificantPlace(in boolean inSignificantPlace);
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d38354d..c6a9203 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1989,85 +1989,86 @@
      * @hide
      */
     @StringDef(value = {
-            DISALLOW_MODIFY_ACCOUNTS,
-            DISALLOW_CONFIG_WIFI,
-            DISALLOW_CONFIG_LOCALE,
-            DISALLOW_INSTALL_APPS,
-            DISALLOW_UNINSTALL_APPS,
-            DISALLOW_SHARE_LOCATION,
+            ALLOW_PARENT_PROFILE_APP_LINKING,
+            DISALLOW_ADD_CLONE_PROFILE,
+            DISALLOW_ADD_MANAGED_PROFILE,
+            DISALLOW_ADD_PRIVATE_PROFILE,
+            DISALLOW_ADD_USER,
+            DISALLOW_ADD_WIFI_CONFIG,
+            DISALLOW_ADJUST_VOLUME,
             DISALLOW_AIRPLANE_MODE,
-            DISALLOW_CONFIG_BRIGHTNESS,
             DISALLOW_AMBIENT_DISPLAY,
-            DISALLOW_CONFIG_SCREEN_TIMEOUT,
-            DISALLOW_INSTALL_UNKNOWN_SOURCES,
-            DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
-            DISALLOW_CONFIG_BLUETOOTH,
+            DISALLOW_APPS_CONTROL,
+            DISALLOW_ASSIST_CONTENT,
+            DISALLOW_AUTOFILL,
+            DISALLOW_BIOMETRIC,
             DISALLOW_BLUETOOTH,
             DISALLOW_BLUETOOTH_SHARING,
-            DISALLOW_USB_FILE_TRANSFER,
-            DISALLOW_CONFIG_CREDENTIALS,
-            DISALLOW_REMOVE_USER,
-            DISALLOW_REMOVE_MANAGED_PROFILE,
-            DISALLOW_DEBUGGING_FEATURES,
-            DISALLOW_CONFIG_VPN,
-            DISALLOW_CONFIG_LOCATION,
-            DISALLOW_CONFIG_DATE_TIME,
-            DISALLOW_CONFIG_TETHERING,
-            DISALLOW_NETWORK_RESET,
-            DISALLOW_FACTORY_RESET,
-            DISALLOW_ADD_USER,
-            DISALLOW_ADD_MANAGED_PROFILE,
-            DISALLOW_ADD_CLONE_PROFILE,
-            DISALLOW_ADD_PRIVATE_PROFILE,
-            ENSURE_VERIFY_APPS,
-            DISALLOW_CONFIG_CELL_BROADCASTS,
-            DISALLOW_CONFIG_MOBILE_NETWORKS,
-            DISALLOW_APPS_CONTROL,
-            DISALLOW_MOUNT_PHYSICAL_MEDIA,
-            DISALLOW_UNMUTE_MICROPHONE,
-            DISALLOW_ADJUST_VOLUME,
-            DISALLOW_OUTGOING_CALLS,
-            DISALLOW_SMS,
-            DISALLOW_FUN,
-            DISALLOW_CREATE_WINDOWS,
-            DISALLOW_SYSTEM_ERROR_DIALOGS,
-            DISALLOW_CROSS_PROFILE_COPY_PASTE,
-            DISALLOW_OUTGOING_BEAM,
-            DISALLOW_WALLPAPER,
-            DISALLOW_SET_WALLPAPER,
-            DISALLOW_SAFE_BOOT,
-            DISALLOW_RECORD_AUDIO,
-            DISALLOW_RUN_IN_BACKGROUND,
             DISALLOW_CAMERA,
-            DISALLOW_UNMUTE_DEVICE,
-            DISALLOW_DATA_ROAMING,
-            DISALLOW_SET_USER_ICON,
-            DISALLOW_OEM_UNLOCK,
-            DISALLOW_UNIFIED_PASSWORD,
-            ALLOW_PARENT_PROFILE_APP_LINKING,
-            DISALLOW_AUTOFILL,
+            DISALLOW_CAMERA_TOGGLE,
+            DISALLOW_CELLULAR_2G,
+            DISALLOW_CHANGE_WIFI_STATE,
+            DISALLOW_CONFIG_BLUETOOTH,
+            DISALLOW_CONFIG_BRIGHTNESS,
+            DISALLOW_CONFIG_CELL_BROADCASTS,
+            DISALLOW_CONFIG_CREDENTIALS,
+            DISALLOW_CONFIG_DATE_TIME,
+            DISALLOW_CONFIG_DEFAULT_APPS,
+            DISALLOW_CONFIG_LOCALE,
+            DISALLOW_CONFIG_LOCATION,
+            DISALLOW_CONFIG_MOBILE_NETWORKS,
+            DISALLOW_CONFIG_PRIVATE_DNS,
+            DISALLOW_CONFIG_SCREEN_TIMEOUT,
+            DISALLOW_CONFIG_TETHERING,
+            DISALLOW_CONFIG_VPN,
+            DISALLOW_CONFIG_WIFI,
             DISALLOW_CONTENT_CAPTURE,
             DISALLOW_CONTENT_SUGGESTIONS,
-            DISALLOW_USER_SWITCH,
-            DISALLOW_SHARE_INTO_MANAGED_PROFILE,
-            DISALLOW_PRINTING,
-            DISALLOW_CONFIG_PRIVATE_DNS,
-            DISALLOW_MICROPHONE_TOGGLE,
-            DISALLOW_CAMERA_TOGGLE,
-            KEY_RESTRICTIONS_PENDING,
-            DISALLOW_BIOMETRIC,
-            DISALLOW_CHANGE_WIFI_STATE,
-            DISALLOW_WIFI_TETHERING,
-            DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
-            DISALLOW_WIFI_DIRECT,
-            DISALLOW_ADD_WIFI_CONFIG,
-            DISALLOW_CELLULAR_2G,
-            DISALLOW_ULTRA_WIDEBAND_RADIO,
+            DISALLOW_CREATE_WINDOWS,
+            DISALLOW_CROSS_PROFILE_COPY_PASTE,
+            DISALLOW_DATA_ROAMING,
+            DISALLOW_DEBUGGING_FEATURES,
+            DISALLOW_FACTORY_RESET,
+            DISALLOW_FUN,
             DISALLOW_GRANT_ADMIN,
+            DISALLOW_INSTALL_APPS,
+            DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
+            DISALLOW_MICROPHONE_TOGGLE,
+            DISALLOW_MODIFY_ACCOUNTS,
+            DISALLOW_MOUNT_PHYSICAL_MEDIA,
             DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
-            DISALLOW_THREAD_NETWORK,
+            DISALLOW_NETWORK_RESET,
+            DISALLOW_OEM_UNLOCK,
+            DISALLOW_OUTGOING_BEAM,
+            DISALLOW_OUTGOING_CALLS,
+            DISALLOW_PRINTING,
+            DISALLOW_RECORD_AUDIO,
+            DISALLOW_REMOVE_MANAGED_PROFILE,
+            DISALLOW_REMOVE_USER,
+            DISALLOW_RUN_IN_BACKGROUND,
+            DISALLOW_SAFE_BOOT,
+            DISALLOW_SET_USER_ICON,
+            DISALLOW_SET_WALLPAPER,
+            DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+            DISALLOW_SHARE_LOCATION,
+            DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
             DISALLOW_SIM_GLOBALLY,
-            DISALLOW_ASSIST_CONTENT,
+            DISALLOW_SMS,
+            DISALLOW_SYSTEM_ERROR_DIALOGS,
+            DISALLOW_THREAD_NETWORK,
+            DISALLOW_ULTRA_WIDEBAND_RADIO,
+            DISALLOW_UNIFIED_PASSWORD,
+            DISALLOW_UNINSTALL_APPS,
+            DISALLOW_UNMUTE_DEVICE,
+            DISALLOW_UNMUTE_MICROPHONE,
+            DISALLOW_USB_FILE_TRANSFER,
+            DISALLOW_USER_SWITCH,
+            DISALLOW_WALLPAPER,
+            DISALLOW_WIFI_DIRECT,
+            DISALLOW_WIFI_TETHERING,
+            ENSURE_VERIFY_APPS,
+            KEY_RESTRICTIONS_PENDING,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index 74428aa..d7389ba 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -30,3 +30,11 @@
     is_fixed_read_only: true
     bug: "276433199"
 }
+
+flag {
+    name: "perfetto_view_capture_tracing"
+    namespace: "windowing_tools"
+    description: "Migrate ViewCapture tracing to Perfetto"
+    is_fixed_read_only: true
+    bug: "323166383"
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a844370..9579614 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,6 +40,7 @@
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
 import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
 import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly;
@@ -5773,7 +5774,7 @@
      */
     private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
 
-    static final float MAX_FRAME_RATE = 140;
+    static final float MAX_FRAME_RATE = 120;
 
     // The preferred frame rate of the view that is mainly used for
     // touch boosting, view velocity handling, and TextureView.
@@ -32237,7 +32238,11 @@
         void decreaseSensitiveViewsCount() {
             mSensitiveViewsCount--;
             if (mSensitiveViewsCount == 0) {
-                mViewRootImpl.notifySensitiveContentAppProtection(false);
+                if (sensitiveContentPrematureProtectionRemovedFix()) {
+                    mViewRootImpl.removeSensitiveContentProtectionOnTransactionCommit();
+                } else {
+                    mViewRootImpl.notifySensitiveContentAppProtection(false);
+                }
             }
             if (mSensitiveViewsCount < 0) {
                 Log.wtf(VIEW_LOG_TAG, "mSensitiveViewsCount is negative" + mSensitiveViewsCount);
@@ -33944,8 +33949,9 @@
     protected int calculateFrameRateCategory() {
         int category;
         switch (getViewRootImpl().intermittentUpdateState()) {
-            case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT ->
-                    category = FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+            case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT -> category =
+                    (sToolkitFrameRateBySizeReadOnlyFlagValue ? FRAME_RATE_CATEGORY_LOW
+                            : FRAME_RATE_CATEGORY_NORMAL) | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
             case ViewRootImpl.INTERMITTENT_STATE_NOT_INTERMITTENT ->
                     category = mSizeBasedFrameRateCategoryAndReason;
             default -> category = mLastFrameRateCategory;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 11ad86c..ab529e6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2654,9 +2654,10 @@
             ViewRootImpl viewRootImpl = getViewRootImpl();
             if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
-                final boolean isDispatchingBack = (viewRootImpl != null
-                        && viewRootImpl.getOnBackInvokedDispatcher().isDispatching());
-                if (!disallowIntercept || isDispatchingBack) { // Allow back to intercept touch
+                final boolean isBackGestureInProgress = (viewRootImpl != null
+                        && viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress());
+                if (!disallowIntercept || isBackGestureInProgress) {
+                    // Allow back to intercept touch
                     intercepted = onInterceptTouchEvent(ev);
                     ev.setAction(action); // restore action in case it was changed
                 } else {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f51d909..1d84375 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -317,6 +317,7 @@
     private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV;
     private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV;
     private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
+    private static final boolean DEBUG_SENSITIVE_CONTENT = false || LOCAL_LOGV;
     private static final int LOGTAG_INPUT_FOCUS = 62001;
     private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004;
 
@@ -4261,7 +4262,9 @@
             updateInfrequentCount();
             setPreferredFrameRate(mPreferredFrameRate);
             setPreferredFrameRateCategory(mPreferredFrameRateCategory);
-            if (!mIsFrameRateConflicted) {
+            if (mPreferredFrameRate > 0
+                    || (mLastPreferredFrameRate != 0 && mPreferredFrameRate == 0)
+            ) {
                 mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
                 mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
                         FRAME_RATE_SETTING_REEVALUATE_TIME);
@@ -4334,6 +4337,10 @@
             if (mSensitiveContentProtectionService == null) {
                 return;
             }
+            if (DEBUG_SENSITIVE_CONTENT) {
+                Log.d(TAG, "Notify sensitive content, package=" + mContext.getPackageName()
+                        + ", token=" + getWindowToken() + ", flag=" + showSensitiveContent);
+            }
             // The window would be blocked during screen share if it shows sensitive content.
             mSensitiveContentProtectionService.setSensitiveContentProtection(
                     getWindowToken(), mContext.getPackageName(), showSensitiveContent);
@@ -4342,6 +4349,24 @@
         }
     }
 
+    /**
+     * Sensitive protection is removed on transaction commit to avoid prematurely removing
+     * the protection.
+     */
+    void removeSensitiveContentProtectionOnTransactionCommit() {
+        if (DEBUG_SENSITIVE_CONTENT) {
+            Log.d(TAG, "Add transaction to remove sensitive content protection, package="
+                    + mContext.getPackageName() + ", token=" + getWindowToken());
+        }
+        Transaction t = new Transaction();
+        t.addTransactionCommittedListener(mExecutor, () -> {
+            if (mAttachInfo.mSensitiveViewsCount == 0) {
+                notifySensitiveContentAppProtection(false);
+            }
+        });
+        applyTransactionOnDraw(t);
+    }
+
     private void notifyContentCaptureEvents() {
         if (!isContentCaptureEnabled()) {
             if (DEBUG_CONTENT_CAPTURE) {
@@ -7228,7 +7253,7 @@
         private int doOnBackKeyEvent(KeyEvent keyEvent) {
             WindowOnBackInvokedDispatcher dispatcher = getOnBackInvokedDispatcher();
             OnBackInvokedCallback topCallback = dispatcher.getTopCallback();
-            if (dispatcher.isDispatching()) {
+            if (dispatcher.isBackGestureInProgress()) {
                 return FINISH_NOT_HANDLED;
             }
             if (topCallback instanceof OnBackAnimationCallback) {
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 16e1415..12bd45a 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -46,6 +46,18 @@
 }
 
 flag {
+  name: "sensitive_content_premature_protection_removed_fix"
+  namespace: "permissions"
+  description: "Bug fix where sensitive content protection is prematurely removed."
+  bug: "336626172"
+  # Referenced in WM where WM starts before DeviceConfig
+  is_fixed_read_only: true
+  metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_arrow_icon_on_hover_when_clickable"
     namespace: "toolkit"
     description: "Enable default arrow icon when hovering on buttons or clickable widgets."
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8174da6..1cdcd20 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1671,7 +1671,6 @@
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
-    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) {
         final Context fallbackContext = ActivityThread.currentApplication();
@@ -1816,7 +1815,6 @@
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
-    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) {
         return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier());
@@ -1858,7 +1856,6 @@
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     @TestApi
-    @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
     @SuppressLint("UserHandle")
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
             @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes,
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index d79903b..fa9458d 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -106,3 +106,13 @@
     }
 }
 
+flag {
+  name: "defer_show_soft_input_until_session_creation"
+  namespace: "input_method"
+  description: "Defers showSoftInput until the IME session has been created."
+  bug: "337766845"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index f643bd4..5430f8f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -382,7 +382,7 @@
     /**
      * Maps Intent ID to RemoteCollectionItems to avoid duplicate items
      */
-    private RemoteCollectionCache mCollectionCache = new RemoteCollectionCache();
+    private @NonNull RemoteCollectionCache mCollectionCache = new RemoteCollectionCache();
 
     /** Cache of ApplicationInfos used by collection items. */
     private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache();
@@ -775,6 +775,16 @@
     }
 
     /**
+     * Return {@code true} only if this {@code RemoteViews} is a legacy list widget that uses
+     * {@code Intent} for inflating child entries.
+     *
+     * @hide
+     */
+    public boolean isLegacyListRemoteViews() {
+        return mCollectionCache.mIdToUriMapping.size() > 0;
+    }
+
+    /**
      * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
      * grants will need to be issued to ensure the recipient of this object is able to render its
      * contents.
@@ -1231,8 +1241,8 @@
     }
 
     private class RemoteCollectionCache {
-        private SparseArray<String> mIdToUriMapping = new SparseArray<>();
-        private HashMap<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
+        private final SparseArray<String> mIdToUriMapping = new SparseArray<>();
+        private final Map<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
 
         RemoteCollectionCache() { }
 
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index cf0c015..e351d6b 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -114,7 +114,7 @@
 
     /** Updates the dispatcher state on a new {@link MotionEvent}. */
     public void onMotionEvent(MotionEvent ev) {
-        if (!isDispatching() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) {
+        if (!isBackGestureInProgress() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) {
             return;
         }
         mTouchTracker.update(ev.getX(), ev.getY(), Float.NaN, Float.NaN);
@@ -246,9 +246,9 @@
     }
 
     /**
-     * Indicates if the dispatcher is actively dispatching to a callback.
+     * Indicates if a user gesture is currently in progress.
      */
-    public boolean isDispatching() {
+    public boolean isBackGestureInProgress() {
         synchronized (mLock) {
             return mTouchTracker.isActive() || mImeDispatchingActive;
         }
@@ -475,12 +475,17 @@
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
             mHandler.post(() -> {
+                final OnBackAnimationCallback callback = getBackAnimationCallback();
+
+                // reset progress animator before dispatching onBackStarted to callback. This
+                // ensures that onBackCancelled (of a previous gesture) is always dispatched
+                // before onBackStarted
+                if (callback != null) mProgressAnimator.reset();
                 mTouchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE);
                 mTouchTracker.setShouldUpdateStartLocation(true);
                 mTouchTracker.setGestureStartLocation(
                         backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge());
 
-                final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
                     callback.onBackStarted(new BackEvent(
                             backEvent.getTouchX(),
@@ -499,14 +504,9 @@
         public void onBackCancelled() {
             mHandler.post(() -> {
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
-                if (callback == null) {
-                    mTouchTracker.reset();
-                    return;
-                }
-                mProgressAnimator.onBackCancelled(() -> {
-                    mTouchTracker.reset();
-                    callback.onBackCancelled();
-                });
+                mTouchTracker.reset();
+                if (callback == null) return;
+                mProgressAnimator.onBackCancelled(callback::onBackCancelled);
             });
         }
 
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 55927cc..e8b4f0b 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -64,3 +64,10 @@
     description: "Hides the App Handle when in fullscreen immersive mode"
     bug: "336368019"
 }
+
+flag {
+    name: "enable_desktop_windowing_quick_switch"
+    namespace: "lse_desktop_experience"
+    description: "Enables quick switch for desktop mode"
+    bug: "338066529"
+}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 7cbacad..8faa4cf 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -23,6 +23,7 @@
 
 /** {@hide} */
 oneway interface IAppWidgetHost {
+    void updateAppWidgetDeferred(int appWidgetId);
     void updateAppWidget(int appWidgetId, in RemoteViews views);
     void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
     void providersChanged();
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index db95012..ee9c3aa 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.inputmethod;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityThread;
@@ -144,7 +145,7 @@
      *
      * @param where Place where the trace was triggered.
      */
-    public abstract void triggerManagerServiceDump(String where);
+    public abstract void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper);
 
     /**
      * Being called while taking a bugreport so that tracing files can be included in the bugreport
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
index 95ed4ed..8a684d3 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
@@ -83,7 +83,7 @@
     }
 
     @Override
-    public void triggerManagerServiceDump(String where) {
+    public void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper) {
         // Intentionally left empty, this is implemented in ImeTracingServerImpl
     }
 
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
index edd74f6..937f94c 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
@@ -18,6 +18,7 @@
 
 import static android.os.Build.IS_USER;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.util.Log;
@@ -116,7 +117,7 @@
     }
 
     @Override
-    public void triggerManagerServiceDump(String where) {
+    public void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper) {
         if (!isEnabled() || !isAvailable()) {
             return;
         }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index f4315e3..74c2325 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -523,8 +523,8 @@
         ViewRootImpl viewRootImpl = getViewRootImpl();
         if (viewRootImpl != null) {
             viewRootImpl.getOnBackInvokedDispatcher().onMotionEvent(event);
-            // Intercept touch if back dispatching is active.
-            if (viewRootImpl.getOnBackInvokedDispatcher().isDispatching()) {
+            // Intercept touch if back gesture is in progress.
+            if (viewRootImpl.getOnBackInvokedDispatcher().isBackGestureInProgress()) {
                 return true;
             }
         }
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 874cc49..ee33eb4 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -99,7 +99,7 @@
     private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>();
     private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>();
 
-    private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
+    private final ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
 
     public PerfettoProtoLogImpl(String viewerConfigFilePath,
             TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 72b98a2..2316f4c 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -529,7 +529,7 @@
 }
 
 static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId,
-                                                  jboolean overrideToPortrait, jint deviceId,
+                                                  jint rotationOverride, jint deviceId,
                                                   jint devicePolicy, jobject info_obj) {
     CameraInfo cameraInfo;
     if (cameraId >= Camera::getNumberOfCameras(deviceId, devicePolicy) || cameraId < 0) {
@@ -538,7 +538,7 @@
         return;
     }
 
-    status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, deviceId, devicePolicy,
+    status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy,
                                         &cameraInfo);
     if (rc != NO_ERROR) {
         jniThrowRuntimeException(env, "Fail to get camera info");
@@ -557,7 +557,7 @@
 // connect to camera service
 static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                                  jint cameraId, jstring clientPackageName,
-                                                 jboolean overrideToPortrait,
+                                                 jint rotationOverride,
                                                  jboolean forceSlowJpegMode, jint deviceId,
                                                  jint devicePolicy) {
     // Convert jstring to String16
@@ -571,7 +571,7 @@
     int targetSdkVersion = android_get_application_target_sdk_version();
     sp<Camera> camera =
             Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
-                            targetSdkVersion, overrideToPortrait, forceSlowJpegMode, deviceId,
+                            targetSdkVersion, rotationOverride, forceSlowJpegMode, deviceId,
                             devicePolicy);
     if (camera == NULL) {
         return -EACCES;
@@ -600,7 +600,7 @@
 
     // Update default display orientation in case the sensor is reverse-landscape
     CameraInfo cameraInfo;
-    status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, deviceId, devicePolicy,
+    status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy,
                                         &cameraInfo);
     if (rc != NO_ERROR) {
         ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc);
@@ -1057,9 +1057,9 @@
 
 static const JNINativeMethod camMethods[] = {
         {"_getNumberOfCameras", "(II)I", (void *)android_hardware_Camera_getNumberOfCameras},
-        {"_getCameraInfo", "(IZIILandroid/hardware/Camera$CameraInfo;)V",
+        {"_getCameraInfo", "(IIIILandroid/hardware/Camera$CameraInfo;)V",
          (void *)android_hardware_Camera_getCameraInfo},
-        {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;ZZII)I",
+        {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZII)I",
          (void *)android_hardware_Camera_native_setup},
         {"native_release", "()V", (void *)android_hardware_Camera_release},
         {"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3ed9f49..12d62cc 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1828,10 +1828,10 @@
   std::string source = "/dev/__properties__/appcompat_override";
   std::string target = "/dev/__properties__";
   if (access(source.c_str(), F_OK) != 0) {
-    fail_fn(CREATE_ERROR("Error accessing %s: %s", source.c_str(), strerror(errno)));
+      return;
   }
   if (access(target.c_str(), F_OK) != 0) {
-    fail_fn(CREATE_ERROR("Error accessing %s: %s", target.c_str(), strerror(errno)));
+      return;
   }
   BindMount(source, target, fail_fn);
   // Reload the system properties file, to ensure new values are read into memory
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 8ae2a9b..6ce4b9d 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -21,16 +21,102 @@
     android:viewportHeight="24">
   <group>
     <clip-path
-        android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"/>
+        android:pathData="
+            M12.6495 17.375
+            C12.3608 17.875 11.6392 17.875 11.3505 17.375
+            L3.98927 4.625
+            C3.70059 4.125 4.06143 3.5 4.63878 3.5
+            L19.3612 3.5
+            C19.9386 3.5 20.2994 4.125 20.0107 4.625
+            L12.6495 17.375
+            Z
+        "/>
     <path
-        android:pathData="M5,14.978h14v9.8h-14z"
+        android:pathData="M5,12 h14v9.8h-14z"
         android:fillColor="#ffffff"/>
     <path
-        android:pathData="M18.722,15.576C18.717,15.548 18.713,15.521 18.708,15.493C18.68,15.324 18.646,15.156 18.605,14.991C18.534,14.701 18.445,14.42 18.339,14.146C18.249,13.915 18.146,13.69 18.033,13.472C17.886,13.192 17.722,12.923 17.539,12.667C17.316,12.354 17.067,12.06 16.795,11.789C16.68,11.676 16.562,11.566 16.44,11.461C16.175,11.233 15.893,11.025 15.595,10.839C15.598,10.834 15.6,10.83 15.602,10.825C15.739,10.59 15.875,10.355 16.012,10.119C16.145,9.889 16.279,9.659 16.412,9.429C16.508,9.264 16.604,9.098 16.699,8.933C16.722,8.894 16.74,8.854 16.753,8.812C16.791,8.696 16.792,8.575 16.762,8.462C16.754,8.434 16.745,8.406 16.734,8.38C16.723,8.353 16.71,8.327 16.695,8.302C16.644,8.216 16.571,8.142 16.479,8.087C16.399,8.039 16.308,8.011 16.215,8.002C16.176,7.999 16.137,7.999 16.098,8.003C16.066,8.007 16.034,8.013 16.002,8.021C15.889,8.051 15.785,8.113 15.703,8.202C15.674,8.235 15.647,8.27 15.624,8.309C15.529,8.475 15.433,8.64 15.337,8.805L14.937,9.495C14.801,9.731 14.664,9.966 14.528,10.202C14.513,10.227 14.498,10.253 14.483,10.279C14.462,10.271 14.442,10.263 14.421,10.255C13.669,9.968 12.853,9.811 12,9.811C11.977,9.811 11.954,9.811 11.931,9.811C11.172,9.819 10.444,9.951 9.764,10.188C9.686,10.215 9.608,10.244 9.531,10.274C9.517,10.25 9.503,10.226 9.489,10.202C9.353,9.966 9.216,9.731 9.08,9.495C8.946,9.265 8.813,9.035 8.679,8.805C8.584,8.64 8.488,8.475 8.392,8.31C8.37,8.271 8.343,8.235 8.314,8.203C8.232,8.113 8.127,8.051 8.014,8.021C7.983,8.013 7.951,8.007 7.919,8.004C7.88,8 7.841,7.999 7.802,8.003C7.709,8.011 7.618,8.039 7.537,8.088C7.446,8.142 7.373,8.217 7.322,8.302C7.307,8.327 7.294,8.353 7.283,8.38C7.271,8.407 7.262,8.434 7.255,8.462C7.225,8.575 7.226,8.697 7.264,8.812C7.277,8.854 7.295,8.894 7.318,8.934C7.413,9.099 7.509,9.264 7.605,9.429C7.738,9.659 7.872,9.889 8.005,10.119C8.141,10.355 8.278,10.59 8.414,10.826C8.415,10.828 8.417,10.83 8.418,10.832C8.143,11.003 7.881,11.192 7.634,11.4C7.486,11.524 7.343,11.654 7.207,11.79C6.935,12.061 6.685,12.354 6.462,12.668C6.279,12.923 6.114,13.192 5.968,13.472C5.855,13.691 5.752,13.915 5.662,14.147C5.556,14.42 5.467,14.702 5.396,14.991C5.355,15.157 5.321,15.324 5.293,15.494C5.288,15.521 5.284,15.549 5.279,15.576C5.264,15.675 5.251,15.774 5.241,15.874L18.759,15.874C18.749,15.774 18.736,15.675 18.72,15.576L18.722,15.576Z"
+        android:pathData="
+            M15.97 10.48
+            C15.97 10.46 15.97 10.45 15.96 10.43
+            C15.95 10.33 15.93 10.23 15.90 10.13
+            C15.86 9.96 15.81 9.79 15.75 9.63
+            C15.69 9.50 15.63 9.36 15.57 9.23
+            C15.48 9.07 15.38 8.91 15.27 8.76
+            C15.14 8.57 14.99 8.40 14.83 8.24
+            C14.76 8.17 14.69 8.11 14.62 8.04
+            C14.47 7.91 14.30 7.78 14.12 7.67
+            C14.12 7.67 14.13 7.67 14.13 7.67
+            C14.21 7.53 14.29 7.39 14.37 7.25
+            C14.45 7.11 14.53 6.98 14.61 6.84
+            C14.66 6.74 14.72 6.64 14.78 6.55
+            C14.79 6.52 14.80 6.50 14.81 6.48
+            C14.83 6.41 14.83 6.33 14.81 6.27
+            C14.81 6.25 14.80 6.24 14.80 6.22
+            C14.79 6.20 14.78 6.19 14.77 6.17
+            C14.74 6.12 14.70 6.08 14.65 6.05
+            C14.60 6.02 14.54 6 14.49 6
+            C14.47 5.99 14.44 5.99 14.42 6
+            C14.40 6 14.38 6 14.36 6.01
+            C14.30 6.02 14.23 6.06 14.19 6.11
+            C14.17 6.13 14.15 6.15 14.14 6.18
+            C14.08 6.28 14.03 6.37 13.97 6.47
+            L13.73 6.88
+            C13.65 7.02 13.57 7.16 13.49 7.30
+            C13.48 7.31 13.47 7.33 13.46 7.34
+            C13.45 7.34 13.44 7.33 13.43 7.33
+            C12.98 7.16 12.50 7.07 12 7.07
+            C11.98 7.07 11.97 7.07 11.95 7.07
+            C11.51 7.07 11.07 7.15 10.67 7.29
+            C10.63 7.31 10.58 7.32 10.53 7.34
+            C10.53 7.33 10.52 7.31 10.51 7.30
+            C10.43 7.16 10.35 7.02 10.27 6.88
+            C10.19 6.74 10.11 6.61 10.03 6.47
+            C9.97 6.37 9.92 6.28 9.86 6.18
+            C9.85 6.15 9.83 6.13 9.81 6.11
+            C9.77 6.06 9.70 6.03 9.64 6.01
+            C9.62 6 9.60 6 9.58 6
+            C9.56 5.99 9.53 5.99 9.51 6
+            C9.46 6 9.40 6.02 9.35 6.05
+            C9.30 6.08 9.26 6.12 9.23 6.17
+            C9.22 6.19 9.21 6.20 9.20 6.22
+            C9.20 6.24 9.19 6.25 9.19 6.27
+            C9.17 6.34 9.17 6.41 9.19 6.48
+            C9.20 6.50 9.21 6.52 9.22 6.55
+            C9.28 6.65 9.34 6.74 9.39 6.84
+            C9.47 6.98 9.55 7.11 9.63 7.25
+            C9.71 7.39 9.79 7.53 9.87 7.67
+            C9.87 7.67 9.87 7.67 9.88 7.67
+            C9.71 7.77 9.56 7.88 9.41 8.01
+            C9.32 8.08 9.24 8.16 9.16 8.24
+            C9 8.40 8.85 8.57 8.72 8.76
+            C8.61 8.91 8.51 9.07 8.43 9.23
+            C8.36 9.36 8.30 9.50 8.24 9.63
+            C8.18 9.79 8.13 9.96 8.09 10.13
+            C8.06 10.23 8.04 10.33 8.03 10.43
+            C8.02 10.45 8.02 10.46 8.02 10.48
+            C8.01 10.54 8 10.60 8 10.65
+            L16 10.65
+            C15.99 10.60 15.98 10.54 15.97 10.48
+            L15.97 10.48
+            Z
+        "
         android:fillColor="#ffffff"/>
   </group>
   <path
-      android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"
+      android:pathData="
+          M12,20.923
+          C10.764,20.923 9.946,20.065 9.131,18.653
+          C8.316,17.241 4.291,10.269 3.476,8.857
+          C2.66,7.445 2.326,6.309 2.944,5.238
+          C3.563,4.167 4.714,3.888 6.344,3.888
+          C7.975,3.888 16.025,3.888 17.656,3.888
+          C19.286,3.888 20.437,4.167 21.056,5.238
+          C21.674,6.309 21.34,7.445 20.524,8.857
+          C19.709,10.269 15.684,17.241 14.869,18.653
+          C14.054,20.065 13.236,20.923 12,20.923
+          H12
+          Z
+      "
       android:strokeWidth="2"
       android:fillColor="#00000000"
       android:strokeColor="#ffffff"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 54c9479..1fca4f8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5859,6 +5859,10 @@
     <string name="dynamic_mode_notification_title">Battery Saver turned on</string>
     <!-- Summary of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
     <string name="dynamic_mode_notification_summary">Reducing battery usage to extend battery life</string>
+    <!-- Title of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
+    <string name="dynamic_mode_notification_title_v2">Battery Saver is on</string>
+    <!-- Summary of notification letting users know why battery saver was turned on automatically [CHAR_LIMIT=NONE]-->
+    <string name="dynamic_mode_notification_summary_v2">Battery Saver is turned on to extend battery life</string>
 
     <!-- Battery saver strings -->
     <!-- The user visible name of the notification channel for battery saver notifications [CHAR_LIMIT=80] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 972d8df..b3d8f39 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4261,7 +4261,9 @@
   <java-symbol type="string" name="battery_saver_charged_notification_summary" />
   <java-symbol type="string" name="dynamic_mode_notification_channel_name" />
   <java-symbol type="string" name="dynamic_mode_notification_title" />
+  <java-symbol type="string" name="dynamic_mode_notification_title_v2" />
   <java-symbol type="string" name="dynamic_mode_notification_summary" />
+  <java-symbol type="string" name="dynamic_mode_notification_summary_v2" />
   <java-symbol type="drawable" name="ic_battery" />
 
   <java-symbol type="bool" name="config_skipSensorAvailable" />
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 32aec1a..72f1119 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -196,7 +196,7 @@
     @RequiresFlagsEnabled({FLAG_VIEW_VELOCITY_API,
             FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
-    public void highVelocity140() throws Throwable {
+    public void highVelocity120() throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
             layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -208,7 +208,7 @@
             mMovingView.setFrameContentVelocity(1_000_000_000f);
             mMovingView.invalidate();
             runAfterDraw(() -> {
-                assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f);
+                assertEquals(120f, mViewRoot.getLastPreferredFrameRate(), 0f);
             });
         });
         waitForAfterDraw();
@@ -488,16 +488,19 @@
         waitForFrameRateCategoryToSettle();
         for (int i = 0; i < 5; i++) {
             int expectedCategory;
-            if (i < 4) {
+            if (i < 2) {
                 // not intermittent yet.
                 // It takes 2 frames of intermittency before Views vote as intermittent.
-                // It takes 4 more frames for the category to drop to the next category.
                 expectedCategory =
                         toolkitFrameRateDefaultNormalReadOnly() ? FRAME_RATE_CATEGORY_NORMAL
                                 : FRAME_RATE_CATEGORY_HIGH;
             } else {
                 // intermittent
-                expectedCategory = FRAME_RATE_CATEGORY_NORMAL;
+                // Even though this is not a small View, step 3 is triggered by this flag, which
+                // brings intermittent to LOW
+                expectedCategory = toolkitFrameRateBySizeReadOnly()
+                        ? FRAME_RATE_CATEGORY_LOW
+                        : FRAME_RATE_CATEGORY_NORMAL;
             }
             mActivityRule.runOnUiThread(() -> {
                 mMovingView.invalidate();
@@ -561,6 +564,26 @@
         assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
     }
 
+    @Test
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+    })
+    public void frameRateResetWithInvalidations() throws Throwable {
+        mMovingView.setRequestedFrameRate(120f);
+        waitForFrameRateCategoryToSettle();
+        mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
+
+        for (int i = 0; i < 120; i++) {
+            mActivityRule.runOnUiThread(() -> {
+                mMovingView.invalidate();
+                runAfterDraw(() -> {});
+            });
+            waitForAfterDraw();
+        }
+
+        assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
+    }
+
     private void runAfterDraw(@NonNull Runnable runnable) {
         Handler handler = new Handler(Looper.getMainLooper());
         mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 7c098f2..a7f8176 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1174,9 +1174,16 @@
 
         // Infrequent update
         Thread.sleep(delay);
+
+        // Even though this is not a small View, step 3 is triggered by this flag, which
+        // brings intermittent to LOW
+        int intermittentExpected = toolkitFrameRateBySizeReadOnly()
+                ? FRAME_RATE_CATEGORY_LOW
+                : FRAME_RATE_CATEGORY_NORMAL;
+
         sInstrumentation.runOnMainSync(() -> {
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+            runAfterDraw(() -> assertEquals(intermittentExpected,
                     mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
@@ -1184,7 +1191,7 @@
         // When the View vote, it's still considered as intermittent update state
         sInstrumentation.runOnMainSync(() -> {
             mView.invalidate();
-            runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+            runAfterDraw(() -> assertEquals(intermittentExpected,
                     mViewRootImpl.getLastPreferredFrameRateCategory()));
         });
         waitForAfterDraw();
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 1a8e75a..50d7f59 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -51,6 +51,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -372,6 +373,30 @@
     }
 
     @Test
+    public void onBackCancelled_calledBeforeOnBackStartedOfNewGesture() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+        OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+
+        waitForIdle();
+        verify(mCallback1).onBackStarted(any(BackEvent.class));
+        clearInvocations(mCallback1);
+
+        callbackInfo.getCallback().onBackCancelled();
+        waitForIdle();
+
+        // simulate start of new gesture while cancel animation is still running
+        callbackInfo.getCallback().onBackStarted(mBackEvent);
+        waitForIdle();
+
+        // verify that onBackCancelled is called before onBackStarted
+        InOrder orderVerifier = Mockito.inOrder(mCallback1);
+        orderVerifier.verify(mCallback1).onBackCancelled();
+        orderVerifier.verify(mCallback1).onBackStarted(any(BackEvent.class));
+    }
+
+    @Test
     public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
 
@@ -399,11 +424,11 @@
 
         callbackInfo.getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        assertTrue(mDispatcher.isDispatching());
+        assertTrue(mDispatcher.isBackGestureInProgress());
 
         callbackInfo.getCallback().onBackInvoked();
         waitForIdle();
-        assertFalse(mDispatcher.isDispatching());
+        assertFalse(mDispatcher.isBackGestureInProgress());
     }
 
     @Test
@@ -418,7 +443,7 @@
 
         callbackInfo.getCallback().onBackStarted(mBackEvent);
         waitForIdle();
-        assertTrue(mDispatcher.isDispatching());
+        assertTrue(mDispatcher.isBackGestureInProgress());
         assertTrue(mDispatcher.mTouchTracker.isActive());
 
         main.runWithScissors(() -> mDispatcher.onMotionEvent(mMotionEvent), 100);
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
index 03cb17e..1d91af5 100644
--- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
@@ -71,7 +71,7 @@
 
         spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
         assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler);
-        verify(mMockContext, times(1)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
+        verify(mMockContext, never()).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
                 eq(null), eq(mMockHandler));
 
         assertThrows(IllegalStateException.class,
@@ -97,7 +97,7 @@
 
     @Test
     public void testPackageMonitorNotRegisterWithoutSupportPackageRestartQuery() throws Exception {
-        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor(false));
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
 
         spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
 
@@ -106,6 +106,16 @@
     }
 
     @Test
+    public void testPackageMonitorRegisterWithSupportPackageRestartQuery() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor(true));
+
+        spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
+
+        verify(mMockContext, times(1)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
+                eq(null), eq(mMockHandler));
+    }
+
+    @Test
     public void testPackageMonitorDoHandlePackageEventUidRemoved() throws Exception {
         PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
 
@@ -487,7 +497,7 @@
         }
 
         public TestPackageMonitor() {
-            super();
+            super(false);
         }
     }
 }
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 319f115..6d31578 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -465,6 +465,11 @@
      * how pixels are stored. This affects the quality (color depth) as
      * well as the ability to display transparent/translucent colors.
      */
+    // It's touched by Graphics.cpp, so we need to make this enum usable on Ravenwood.
+    // Otherwise, all the ctors would throw, which would make the class unloadable
+    // because the static initializer needs the enum members because of `sConfigs`.
+    // TODO: Remove it once we expose the outer class.
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public enum Config {
         // these native values must match up with the enum in SkBitmap.h
 
diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java
index 1045464..994fb2d 100644
--- a/graphics/java/android/graphics/Interpolator.java
+++ b/graphics/java/android/graphics/Interpolator.java
@@ -18,6 +18,9 @@
 
 import android.os.SystemClock;
 
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+        android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public class Interpolator {
 
     public Interpolator(int valueCount) {
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index bc58feb..fbb690c 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -28,6 +28,9 @@
 /**
  * The Matrix class holds a 3x3 matrix for transforming coordinates.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+        android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public class Matrix {
 
     public static final int MSCALE_X = 0;   //!< use with getValues/setValues
@@ -229,7 +232,7 @@
     private static class NoImagePreloadHolder {
         public static final NativeAllocationRegistry sRegistry =
                 NativeAllocationRegistry.createMalloced(
-                Matrix.class.getClassLoader(), nGetNativeFinalizer());
+                Matrix.class.getClassLoader(), nGetNativeFinalizerWrapper());
     }
 
     private final long native_instance;
@@ -238,7 +241,7 @@
      * Create an identity matrix
      */
     public Matrix() {
-        native_instance = nCreate(0);
+        native_instance = nCreateWrapper(0);
         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
     }
 
@@ -248,7 +251,7 @@
      * @param src The matrix to copy into this matrix
      */
     public Matrix(Matrix src) {
-        native_instance = nCreate(src != null ? src.native_instance : 0);
+        native_instance = nCreateWrapper(src != null ? src.native_instance : 0);
         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
     }
 
@@ -846,6 +849,34 @@
         return native_instance;
     }
 
+    /**
+     * Wrapper method we use to switch to ExtraNatives.nCreate(src) only on Ravenwood.
+     *
+     * @see ExtraNatives
+     */
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static long nCreateWrapper(long src) {
+        return nCreate(src);
+    }
+
+    private static long nCreateWrapper$ravenwood(long src) {
+        return ExtraNatives.nCreate(src);
+    }
+
+    /**
+     * Wrapper method we use to switch to ExtraNatives.nGetNativeFinalizer(src) only on Ravenwood.
+     *
+     * @see ExtraNatives
+     */
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static long nGetNativeFinalizerWrapper() {
+        return nGetNativeFinalizer();
+    }
+
+    private static long nGetNativeFinalizerWrapper$ravenwood() {
+        return ExtraNatives.nGetNativeFinalizer();
+    }
+
     // ------------------ Regular JNI ------------------------
 
     private static native long nCreate(long nSrc_or_zero);
@@ -943,4 +974,25 @@
     private static native float nMapRadius(long nObject, float radius);
     @CriticalNative
     private static native boolean nEquals(long nA, long nB);
+
+    /**
+     * Due to b/337329128, native methods that are called by the static initializers cannot be
+     * in the same class when running on a host side JVM (such as on Ravenwood and Android Studio).
+     *
+     * There are two methods that are called by the static initializers (either directly or
+     * indirectly) in this class, namely nCreate() and nGetNativeFinalizer(). On Ravenwood
+     * these methods can't be on the Matrix class itself, so we use a nested class to host them.
+     *
+     * We still keep the original nCreate() method and call it on non-ravenwood environment,
+     * in order to avoid problems in downstream (such as Android Studio).
+     *
+     * @see #nCreateWrapper(long)
+     * @see #nGetNativeFinalizerWrapper()
+     *
+     * TODO(b/337110712) Clean it up somehow. (remove the original nCreate() and unify the code?)
+     */
+    private static class ExtraNatives {
+        static native long nCreate(long nSrc_or_zero);
+        static native long nGetNativeFinalizer();
+    }
 }
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index deb89fa..073307c 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -36,11 +36,16 @@
  * (based on the paint's Style), or it can be used for clipping or to draw
  * text on a path.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+        android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public class Path {
-
-    private static final NativeAllocationRegistry sRegistry =
-            NativeAllocationRegistry.createMalloced(
-                Path.class.getClassLoader(), nGetFinalizer());
+    // See b/337329128 for why we need an inner class here.
+    private static class NoImagePreloadHolder {
+        static final NativeAllocationRegistry sRegistry =
+                NativeAllocationRegistry.createMalloced(
+                        Path.class.getClassLoader(), nGetFinalizer());
+    }
 
     /**
      * @hide
@@ -52,7 +57,7 @@
      */
     public Path() {
         mNativePath = nInit();
-        sRegistry.registerNativeAllocation(this, mNativePath);
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePath);
     }
 
     /**
@@ -62,7 +67,7 @@
      */
     public Path(@Nullable Path src) {
         mNativePath = nInit(src != null ? src.mNativePath : 0);
-        sRegistry.registerNativeAllocation(this, mNativePath);
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePath);
     }
 
     /**
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 8c42547..7f936f2 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -22,7 +22,6 @@
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.os.Process;
-import android.security.KeyStore;
 import android.security.keymaster.KeymasterDefs;
 
 import libcore.util.EmptyArray;
@@ -1008,13 +1007,20 @@
     public static final int NAMESPACE_LOCKSETTINGS = 103;
 
     /**
+     * The legacy UID that corresponds to {@link #NAMESPACE_APPLICATION}.
+     * In new code, prefer to work with Keystore namespaces directly.
+     * @hide
+     */
+    public static final int UID_SELF = -1;
+
+    /**
      * For legacy support, translate namespaces into known UIDs.
      * @hide
      */
     public static int namespaceToLegacyUid(@Namespace int namespace) {
         switch (namespace) {
             case NAMESPACE_APPLICATION:
-                return KeyStore.UID_SELF;
+                return UID_SELF;
             case NAMESPACE_WIFI:
                 return Process.WIFI_UID;
             default:
@@ -1029,7 +1035,7 @@
      */
     public static @Namespace int legacyUidToNamespace(int uid) {
         switch (uid) {
-            case KeyStore.UID_SELF:
+            case UID_SELF:
                 return NAMESPACE_APPLICATION;
             case Process.WIFI_UID:
                 return NAMESPACE_WIFI;
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c2c90c8..c8bfe7a4 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -420,7 +420,7 @@
     <dimen name="freeform_decor_caption_height">42dp</dimen>
 
     <!-- Height of desktop mode caption for freeform tasks. -->
-    <dimen name="desktop_mode_freeform_decor_caption_height">42dp</dimen>
+    <dimen name="desktop_mode_freeform_decor_caption_height">40dp</dimen>
 
     <!-- Height of desktop mode caption for fullscreen tasks. -->
     <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index e7d9812..c5f23a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -373,6 +373,10 @@
 
             if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
                     && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
+                if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
+                    // Minimized freeform tasks should not be shown at all.
+                    continue;
+                }
                 // Freeform tasks will be added as a separate entry
                 if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
                     mostRecentFreeformTaskIndex = recentTasks.size();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 40b59c1..5cf9be4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -395,6 +395,51 @@
     }
 
     @Test
+    public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() {
+        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+                DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isEnabled()).thenReturn(true);
+
+        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+        setRawList(t1, t2, t3, t4, t5);
+
+        when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+        when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true);
+
+        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+        // 2 freeform tasks should be grouped into one, 1 task should be skipped, 3 total recents
+        // entries
+        assertEquals(3, recentTasks.size());
+        GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+        GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
+        GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+
+        // Check that groups have expected types
+        assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
+        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+
+        // Check freeform group entries
+        assertEquals(2, freeformGroup.getTaskInfoList().size());
+        assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
+        assertEquals(t5, freeformGroup.getTaskInfoList().get(1));
+
+        // Check single entries
+        assertEquals(t2, singleGroup1.getTaskInfo1());
+        assertEquals(t4, singleGroup2.getTaskInfo1());
+
+        mockitoSession.finishMocking();
+    }
+
+    @Test
     public void testRemovedTaskRemovesSplit() {
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 8315c4c..07e97f8 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -211,11 +211,7 @@
 static jfieldID gRegion_nativeInstanceID;
 static jmethodID gRegion_constructorMethodID;
 
-static jclass    gByte_class;
-static jobject   gVMRuntime;
-static jclass    gVMRuntime_class;
-static jmethodID gVMRuntime_newNonMovableArray;
-static jmethodID gVMRuntime_addressOf;
+static jclass gByte_class;
 
 static jclass gColorSpace_class;
 static jmethodID gColorSpace_getMethodID;
@@ -789,13 +785,6 @@
     gByte_class = (jclass) env->NewGlobalRef(
         env->GetStaticObjectField(c, env->GetStaticFieldID(c, "TYPE", "Ljava/lang/Class;")));
 
-    gVMRuntime_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "dalvik/system/VMRuntime"));
-    m = env->GetStaticMethodID(gVMRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
-    gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));
-    gVMRuntime_newNonMovableArray = GetMethodIDOrDie(env, gVMRuntime_class, "newNonMovableArray",
-                                                     "(Ljava/lang/Class;I)Ljava/lang/Object;");
-    gVMRuntime_addressOf = GetMethodIDOrDie(env, gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");
-
     gColorSpace_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace"));
     gColorSpace_getMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class,
             "get", "(Landroid/graphics/ColorSpace$Named;)Landroid/graphics/ColorSpace;");
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index ca667b0..c0d791a 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -376,11 +376,24 @@
     {"nEquals", "(JJ)Z", (void*) SkMatrixGlue::equals}
 };
 
+static const JNINativeMethod extra_methods[] = {
+        {"nGetNativeFinalizer", "()J", (void*)SkMatrixGlue::getNativeFinalizer},
+        {"nCreate", "(J)J", (void*)SkMatrixGlue::create},
+};
+
 static jclass sClazz;
 static jfieldID sNativeInstanceField;
 static jmethodID sCtor;
 
 int register_android_graphics_Matrix(JNIEnv* env) {
+    // Methods only used on Ravenwood (for now). See the javadoc on Matrix$ExtraNativesx
+    // for why we need it.
+    //
+    // We don't need it on non-ravenwood, but we don't (yet) have a way to detect ravenwood
+    // environment, so we just always run it.
+    RegisterMethodsOrDie(env, "android/graphics/Matrix$ExtraNatives", extra_methods,
+                         NELEM(extra_methods));
+
     int result = RegisterMethodsOrDie(env, "android/graphics/Matrix", methods, NELEM(methods));
 
     jclass clazz = FindClassOrDie(env, "android/graphics/Matrix");
diff --git a/location/lib/java/com/android/location/provider/SignificantPlaceProvider.java b/location/lib/java/com/android/location/provider/SignificantPlaceProvider.java
new file mode 100644
index 0000000..0b39a9a
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/SignificantPlaceProvider.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.location.provider;
+
+import android.annotation.Nullable;
+import android.app.trust.TrustManager;
+import android.hardware.location.ISignificantPlaceProvider;
+import android.hardware.location.ISignificantPlaceProviderManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** @hide */
+public class SignificantPlaceProvider {
+
+    public static final String ACTION = TrustManager.ACTION_BIND_SIGNIFICANT_PLACE_PROVIDER;
+
+    private final IBinder mBinder;
+
+    // write locked on mBinder, read lock is optional depending on atomicity requirements
+    @Nullable private volatile ISignificantPlaceProviderManager mManager;
+
+    @GuardedBy("mBinder")
+    private boolean mInSignificantPlace = false;
+
+    public SignificantPlaceProvider() {
+        mBinder = new Service();
+        mManager = null;
+    }
+
+    public IBinder getBinder() {
+        return mBinder;
+    }
+
+    /** Set whether the device is currently in a trusted location. */
+    public void setInSignificantPlace(boolean inSignificantPlace) {
+        synchronized (mBinder) {
+            if (inSignificantPlace == mInSignificantPlace) {
+                return;
+            }
+
+            mInSignificantPlace = inSignificantPlace;
+        }
+
+        ISignificantPlaceProviderManager manager = mManager;
+        if (manager != null) {
+            try {
+                manager.setInSignificantPlace(inSignificantPlace);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    private final class Service extends ISignificantPlaceProvider.Stub {
+
+        Service() {}
+
+        @Override
+        public void setSignificantPlaceProviderManager(ISignificantPlaceProviderManager manager) {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                return;
+            }
+
+            synchronized (mBinder) {
+                if (mInSignificantPlace) {
+                    try {
+                        manager.setInSignificantPlace(true);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+
+                mManager = manager;
+            }
+        }
+    }
+}
diff --git a/media/OWNERS b/media/OWNERS
index 1e5a458..5e39195 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -11,6 +11,7 @@
 nchalko@google.com
 philburk@google.com
 quxiangfang@google.com
+shuzhenwang@google.com
 wonsik@google.com
 
 # go/android-fwk-media-solutions for info on areas of ownership.
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/OWNERS b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/OWNERS
new file mode 100644
index 0000000..f4f9c08
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 1344
+include platform/frameworks/base:/media/OWNERS
+
+# Camera
+per-file *Camera* = file:platform/frameworks/av:/camera/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index fc96896..353366d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -89,7 +89,8 @@
     public void testCameraInfo() throws Exception {
         for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
             CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId,
-                    /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+                    ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT,
+                    DEVICE_POLICY_DEFAULT);
             assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
             assertTrue("Orientation was not set for camera " + cameraId,
                     info.info.orientation != -1);
@@ -164,7 +165,7 @@
                             ICameraService.USE_CALLING_UID,
                             ICameraService.USE_CALLING_PID,
                             getContext().getApplicationInfo().targetSdkVersion,
-                            /*overrideToPortrait*/false,
+                            ICameraService.ROTATION_OVERRIDE_NONE,
                             /*forceSlowJpegMode*/false,
                             DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
@@ -266,7 +267,8 @@
                         clientPackageName, clientAttributionTag,
                         ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/,
                         getContext().getApplicationInfo().targetSdkVersion,
-                        /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+                        ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT,
+                        DEVICE_POLICY_DEFAULT);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
             Log.v(TAG, String.format("Camera %s connected", cameraId));
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index dc8647f..6cf2a41 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -248,7 +248,7 @@
         mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
                 clientPackageName, clientAttributionTag, ICameraService.USE_CALLING_UID,
                 /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
-                /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+                ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
         assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
@@ -415,7 +415,8 @@
     @SmallTest
     public void testCameraCharacteristics() throws RemoteException {
         CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId,
-                getContext().getApplicationInfo().targetSdkVersion, /*overrideToPortrait*/false,
+                getContext().getApplicationInfo().targetSdkVersion,
+                ICameraService.ROTATION_OVERRIDE_NONE,
                 DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
 
         assertFalse(info.isEmpty());
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index 169c330..57bde56 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -29,6 +29,7 @@
 import android.os.UserManager;
 import android.permission.PermissionManager;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.IconDrawableFactory;
 import android.util.Log;
 
@@ -127,6 +128,7 @@
         final long now = mClock.millis();
         final UserManager um = mContext.getSystemService(UserManager.class);
         final List<UserHandle> profiles = um.getUserProfiles();
+        ArrayMap<UserHandle, Boolean> shouldIncludeAppsByUsers = new ArrayMap<>();
 
         for (int i = 0; i < appOpsCount; ++i) {
             AppOpsManager.PackageOps ops = appOps.get(i);
@@ -134,9 +136,13 @@
             int uid = ops.getUid();
             UserHandle user = UserHandle.getUserHandleForUid(uid);
 
+            if (!shouldIncludeAppsByUsers.containsKey(user)) {
+                shouldIncludeAppsByUsers.put(user, shouldHideUser(um, user));
+            }
+
             // Don't show apps belonging to background users except for profiles that shouldn't
             // be shown in quiet mode.
-            if (!profiles.contains(user) || isHideInQuietEnabledForProfile(um, user)) {
+            if (!profiles.contains(user) || !shouldIncludeAppsByUsers.get(user)) {
                 continue;
             }
 
@@ -200,7 +206,7 @@
         return accesses;
     }
 
-    private boolean isHideInQuietEnabledForProfile(UserManager userManager, UserHandle userHandle) {
+    private boolean shouldHideUser(UserManager userManager, UserHandle userHandle) {
         if (android.multiuser.Flags.enablePrivateSpaceFeatures()
                 && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
             return userManager.isQuietModeEnabled(userHandle)
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e2af631..65c5708 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -69,6 +69,219 @@
     visibility: ["//visibility:private"],
 }
 
+filegroup {
+    name: "SystemUI-tests-robofiles",
+    srcs: [
+        "tests/src/**/*.kt",
+        "tests/src/**/*.java",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// We are running robolectric tests in the tests directory as well as
+// multivalent tests.  If you add a test, and it doesn't run in robolectric,
+// it should be added to this exclusion list. go/multivalent-tests
+filegroup {
+    name: "SystemUI-tests-broken-robofiles",
+    srcs: [
+        "tests/src/**/*DeviceOnlyTest.java",
+        "tests/src/**/*DeviceOnlyTest.kt",
+        "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
+        "tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
+        "tests/src/**/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt",
+        "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
+        "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
+        "tests/src/**/systemui/controls/start/ControlsStartableTest.kt",
+        "tests/src/**/systemui/haptics/slider/SliderStateTrackerTest.kt",
+        "tests/src/**/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt",
+        "tests/src/**/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt",
+        "tests/src/**/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt",
+        "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt",
+        "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
+        "tests/src/**/systemui/keyguard/ResourceTrimmerTest.kt",
+        "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
+        "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt",
+        "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
+        "tests/src/**/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt",
+        "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
+        "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
+        "tests/src/**/systemui/qs/tiles/DeviceControlsTileTest.kt",
+        "tests/src/**/systemui/screenshot/ActionExecutorTest.kt",
+        "tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
+        "tests/src/**/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt",
+        "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
+        "tests/src/**/systemui/statusbar/commandline/CommandRegistryTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt",
+        "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
+        "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt",
+        "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
+        "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt",
+        "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
+        "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
+        "tests/src/**/keyguard/ClockEventControllerTest.kt",
+        "tests/src/**/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt",
+        "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.kt",
+        "tests/src/**/keyguard/LegacyLockIconViewControllerTest.java",
+        "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
+        "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
+        "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
+        "tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt",
+        "tests/src/**/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt",
+        "tests/src/**/systemui/controls/controller/ControlsBindingControllerImplTest.kt",
+        "tests/src/**/systemui/controls/controller/ControlsControllerImplTest.kt",
+        "tests/src/**/systemui/controls/controller/DeletionJobServiceTest.kt",
+        "tests/src/**/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt",
+        "tests/src/**/systemui/controls/ui/ControlsUiControllerImplTest.kt",
+        "tests/src/**/systemui/controls/ui/ControlViewHolderTest.kt",
+        "tests/src/**/systemui/controls/ui/SelectionItemTest.kt",
+        "tests/src/**/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt",
+        "tests/src/**/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt",
+        "tests/src/**/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt",
+        "tests/src/**/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt",
+        "tests/src/**/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt",
+        "tests/src/**/systemui/media/controls/ui/controller/MediaControlPanelTest.kt",
+        "tests/src/**/systemui/media/controls/ui/controller/MediaViewControllerTest.kt",
+        "tests/src/**/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt",
+        "tests/src/**/systemui/media/controls/ui/MediaPlayerDataTest.kt",
+        "tests/src/**/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt",
+        "tests/src/**/systemui/navigationbar/gestural/BackPanelControllerTest.kt",
+        "tests/src/**/systemui/notetask/NoteTaskControllerTest.kt",
+        "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
+        "tests/src/**/systemui/power/domain/interactor/PowerInteractorTest.kt",
+        "tests/src/**/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt",
+        "tests/src/**/systemui/privacy/PrivacyItemControllerTest.kt",
+        "tests/src/**/systemui/qs/external/CustomTileStatePersisterTest.kt",
+        "tests/src/**/systemui/qs/external/TileRequestDialogTest.kt",
+        "tests/src/**/systemui/qs/external/TileServiceRequestControllerTest.kt",
+        "tests/src/**/systemui/qs/tileimpl/TilesStatesTextTest.kt",
+        "tests/src/**/systemui/qs/tiles/AlarmTileTest.kt",
+        "tests/src/**/systemui/qs/tiles/BluetoothTileTest.kt",
+        "tests/src/**/systemui/screenshot/ScreenshotPolicyImplTest.kt",
+        "tests/src/**/systemui/settings/DisplayTrackerImplTest.kt",
+        "tests/src/**/systemui/settings/UserFileManagerImplTest.kt",
+        "tests/src/**/systemui/settings/UserTrackerImplReceiveTest.kt",
+        "tests/src/**/systemui/settings/UserTrackerImplTest.kt",
+        "tests/src/**/systemui/shade/GlanceableHubContainerControllerTest.kt",
+        "tests/src/**/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt",
+        "tests/src/**/systemui/shade/NotificationsQSContainerControllerTest.kt",
+        "tests/src/**/systemui/shade/ShadeExpansionStateManagerTest.kt",
+        "tests/src/**/systemui/shade/ShadeHeaderControllerTest.kt",
+        "tests/src/**/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt",
+        "tests/src/**/systemui/statusbar/commandline/CommandParserTest.kt",
+        "tests/src/**/systemui/statusbar/connectivity/MobileStateTest.kt",
+        "tests/src/**/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt",
+        "tests/src/**/systemui/statusbar/gesture/GenericGestureDetectorTest.kt",
+        "tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
+        "tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt",
+        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/RoundableTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/TextPrecomputerTest.kt",
+        "tests/src/**/systemui/statusbar/phone/FoldStateListenerTest.kt",
+        "tests/src/**/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
+        "tests/src/**/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt",
+        "tests/src/**/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt",
+        "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt",
+        "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
+        "tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
+        "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
+        "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
+        "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
+        "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.java",
+        "tests/src/**/keyguard/CarrierTextManagerTest.java",
+        "tests/src/**/systemui/ScreenDecorationsTest.java",
+        "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+        "tests/src/**/systemui/shared/system/RemoteTransitionTest.java",
+        "tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java",
+        "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt",
+        "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt",
+        "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt",
+        "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt",
+        "tests/src/**/systemui/DisplayCutoutBaseViewTest.kt",
+        "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java",
+        "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java",
+        "tests/src/**/systemui/qs/tiles/HotspotTileTest.java",
+        "tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java",
+        "tests/src/**/systemui/recents/OverviewProxyServiceTest.kt",
+        "tests/src/**/systemui/stylus/StylusManagerTest.kt",
+        "tests/src/**/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java",
+        "tests/src/**/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java",
+        "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
+        "tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java",
+        "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java",
+        "tests/src/**/systemui/wmshell/BubblesTest.java",
+        "tests/src/**/systemui/biometrics/AuthRippleControllerTest.kt",
+        "tests/src/**/keyguard/KeyguardAbsKeyInputViewControllerTest.java",
+        "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
+        "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
+        "tests/src/**/systemui/doze/DozeScreenStateTest.java",
+        "tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
+        "tests/src/**/systemui/media/dialog/MediaOutputControllerTest.java",
+        "tests/src/**/systemui/navigationbar/NavigationBarTest.java",
+        "tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
+        "tests/src/**/systemui/power/PowerUITest.java",
+        "tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
+        "tests/src/**/systemui/qs/QSImplTest.java",
+        "tests/src/**/systemui/qs/QSSecurityFooterTest.java",
+        "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java",
+        "tests/src/**/systemui/qs/tiles/QuickAccessWalletTileTest.java",
+        "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
+        "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
+        "tests/src/**/systemui/statusbar/CommandQueueTest.java",
+        "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java",
+        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerBaseTest.java",
+        "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
+        "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
+        "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
+        "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
+        "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
+        "tests/src/**/systemui/toast/ToastUITest.java",
+        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerDataTest.java",
+        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java",
+        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
+        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerWifiTest.java",
+    ],
+    visibility: ["//visibility:private"],
+}
+
 //Create a library to expose SystemUI's resources to other modules.
 android_library {
     name: "SystemUI-res",
@@ -433,6 +646,21 @@
     plugins: ["dagger2-compiler"],
 }
 
+java_library {
+    name: "RoboTestLibraries",
+    static_libs: [
+        "dagger2",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.core_core-animation-testing",
+        "androidx.test.ext.junit",
+        "inline-mockito-robolectric-prebuilt",
+        "platform-parametric-runner-lib",
+        "SystemUICustomizationTestUtils",
+        "kotlin-test",
+        "kosmos",
+    ],
+}
+
 android_robolectric_test {
     name: "SystemUiRoboTests",
     srcs: [
@@ -442,14 +670,40 @@
         ":SystemUI-tests-multivalent",
     ],
     static_libs: [
-        "dagger2",
-        "androidx.test.uiautomator_uiautomator",
-        "androidx.core_core-animation-testing",
-        "androidx.test.ext.junit",
-        "inline-mockito-robolectric-prebuilt",
-        "platform-parametric-runner-lib",
-        "SystemUICustomizationTestUtils",
-        "kosmos",
+        "RoboTestLibraries",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "truth",
+    ],
+
+    upstream: true,
+
+    instrumentation_for: "SystemUIRobo-stub",
+    java_resource_dirs: ["tests/robolectric/config"],
+    plugins: [
+        "dagger2-compiler",
+    ],
+}
+
+// in-place tests which use Robolectric in the tests directory
+// instead of multivalentTests
+android_robolectric_test {
+    name: "SystemUiRoboTestsInplace",
+    srcs: [
+        "tests/robolectric/src/**/*.kt",
+        "tests/robolectric/src/**/*.java",
+        ":SystemUI-tests-utils",
+        ":SystemUI-tests-multivalent",
+        ":SystemUI-tests-robofiles",
+    ],
+    exclude_srcs: [
+        ":SystemUI-tests-broken-robofiles",
+    ],
+    static_libs: [
+        "RoboTestLibraries",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e7226b0..fd90bd9 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -803,6 +803,16 @@
 }
 
 flag {
+    name: "shade_collapse_activity_launch_fix"
+    namespace: "systemui"
+    description: "Avoid collapsing the shade on activity launch if it is already collapsed, as this causes a flicker."
+    bug: "331591373"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "slice_broadcast_relay_in_background"
     namespace: "systemui"
     description: "Move handling of slice broadcast relay broadcasts to background threads"
@@ -838,3 +848,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "communal_bouncer_do_not_modify_plugin_open"
+  namespace: "systemui"
+  description: "do not modify notification shade when handling bouncer expansion."
+  bug: "338252661"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 9ad0fc5..fd79f62 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -191,14 +191,20 @@
         // so we have to take the optical insets into account.
         ghostedView.getLocationOnScreen(ghostedViewLocation)
         val insets = backgroundInsets
-        state.top = ghostedViewLocation[1] + insets.top
+        val boundCorrections: Rect =
+            if (ghostedView is LaunchableView) {
+                ghostedView.getPaddingForLaunchAnimation()
+            } else {
+                Rect()
+            }
+        state.top = ghostedViewLocation[1] + insets.top + boundCorrections.top
         state.bottom =
             ghostedViewLocation[1] + (ghostedView.height * ghostedView.scaleY).roundToInt() -
-                insets.bottom
-        state.left = ghostedViewLocation[0] + insets.left
+                insets.bottom + boundCorrections.bottom
+        state.left = ghostedViewLocation[0] + insets.left + boundCorrections.left
         state.right =
             ghostedViewLocation[0] + (ghostedView.width * ghostedView.scaleX).roundToInt() -
-                insets.right
+                insets.right + boundCorrections.right
     }
 
     override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index da6ccaa..330ab0f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.animation
 
+import android.graphics.Rect
 import android.view.View
 
 /** A view that can expand/launch into an app or a dialog. */
@@ -41,6 +42,9 @@
 
     /** Perform an action when the activity launch animation ends */
     fun onActivityLaunchAnimationEnd() {}
+
+    /** Provide an optional correction applied to the visible area during a launch animation */
+    fun getPaddingForLaunchAnimation(): Rect = Rect()
 }
 
 /** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index dff9b3b..a592aa9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -24,7 +24,11 @@
 import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
 import android.widget.FrameLayout
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.BorderStroke
@@ -83,10 +87,13 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.scale
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ColorMatrix
+import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.key.onPreviewKeyEvent
 import androidx.compose.ui.input.pointer.motionEventSpy
@@ -289,23 +296,21 @@
                 removeEnabled = removeButtonEnabled
             )
         }
+        if (currentPopup == PopupType.CtaTile) {
+            PopupOnDismissCtaTile(viewModel::onHidePopup)
+        }
 
-        if (currentPopup != null) {
-            when (currentPopup) {
-                PopupType.CtaTile -> {
-                    PopupOnDismissCtaTile(viewModel::onHidePopup)
-                }
-                PopupType.CustomizeWidgetButton -> {
-                    ButtonToEditWidgets(
-                        onClick = {
-                            viewModel.onHidePopup()
-                            viewModel.onOpenWidgetEditor(selectedKey.value)
-                        },
-                        onHide = { viewModel.onHidePopup() }
-                    )
-                }
-                null -> {}
-            }
+        AnimatedVisibility(
+            visible = currentPopup == PopupType.CustomizeWidgetButton,
+            modifier = Modifier.fillMaxSize()
+        ) {
+            ButtonToEditWidgets(
+                onClick = {
+                    viewModel.onHidePopup()
+                    viewModel.onOpenWidgetEditor(selectedKey.value)
+                },
+                onHide = { viewModel.onHidePopup() }
+            )
         }
 
         if (viewModel is CommunalViewModel && dialogFactory != null) {
@@ -654,30 +659,67 @@
     }
 }
 
+@OptIn(ExperimentalAnimationApi::class)
 @Composable
-private fun ButtonToEditWidgets(
+private fun AnimatedVisibilityScope.ButtonToEditWidgets(
     onClick: () -> Unit,
     onHide: () -> Unit,
 ) {
-    Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+    Popup(
+        alignment = Alignment.TopCenter,
+        offset = IntOffset(0, 40),
+        onDismissRequest = onHide,
+    ) {
         val colors = LocalAndroidColorScheme.current
         Button(
             modifier =
-                Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+                Modifier.height(56.dp)
+                    .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
+                    .animateEnterExit(
+                        enter =
+                            fadeIn(
+                                initialAlpha = 0f,
+                                animationSpec = tween(durationMillis = 500, easing = LinearEasing)
+                            ),
+                        exit =
+                            fadeOut(
+                                animationSpec = tween(durationMillis = 500, easing = LinearEasing)
+                            )
+                    )
+                    .background(colors.secondary, RoundedCornerShape(50.dp)),
             onClick = onClick,
         ) {
-            Icon(
-                imageVector = Icons.Outlined.Widgets,
-                contentDescription = stringResource(R.string.button_to_configure_widgets_text),
-                tint = colors.onSecondary,
-                modifier = Modifier.size(20.dp)
-            )
-            Spacer(modifier = Modifier.size(8.dp))
-            Text(
-                text = stringResource(R.string.button_to_configure_widgets_text),
-                style = MaterialTheme.typography.titleSmall,
-                color = colors.onSecondary,
-            )
+            Row(
+                modifier =
+                    Modifier.animateEnterExit(
+                        enter =
+                            fadeIn(
+                                animationSpec =
+                                    tween(
+                                        durationMillis = 167,
+                                        delayMillis = 500,
+                                        easing = LinearEasing
+                                    )
+                            ),
+                        exit =
+                            fadeOut(
+                                animationSpec = tween(durationMillis = 167, easing = LinearEasing)
+                            )
+                    )
+            ) {
+                Icon(
+                    imageVector = Icons.Outlined.Widgets,
+                    contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+                    tint = colors.onSecondary,
+                    modifier = Modifier.size(20.dp)
+                )
+                Spacer(modifier = Modifier.size(8.dp))
+                Text(
+                    text = stringResource(R.string.button_to_configure_widgets_text),
+                    style = MaterialTheme.typography.titleSmall,
+                    color = colors.onSecondary
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 985d3a1..6e987bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -65,7 +65,6 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.modifiers.height
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
@@ -157,6 +156,7 @@
 fun SceneScope.NotificationScrollingStack(
     viewModel: NotificationsPlaceholderViewModel,
     maxScrimTop: () -> Float,
+    shouldPunchHoleBehindScrim: Boolean,
     modifier: Modifier = Modifier,
 ) {
     val density = LocalDensity.current
@@ -233,7 +233,8 @@
                     // in step with the transition so that it is 0 when it completes.
                     if (
                         scrimOffset.value < 0 &&
-                            layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone)
+                            layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
+                            layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
                     ) {
                         IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
                     } else {
@@ -246,7 +247,7 @@
                                 scrimCornerRadius,
                                 screenCornerRadius,
                                 { expansionFraction },
-                                layoutState.isNotificationScrimTransitioning(),
+                                shouldPunchHoleBehindScrim,
                             )
                             .let { scrimRounding.value.toRoundedCornerShape(it) }
                     clip = true
@@ -270,18 +271,20 @@
     ) {
         // Creates a cutout in the background scrim in the shape of the notifications scrim.
         // Only visible when notif scrim alpha < 1, during shade expansion.
-        Spacer(
-            modifier =
-                Modifier.fillMaxSize().drawBehind {
-                    drawRect(Color.Black, blendMode = BlendMode.DstOut)
-                }
-        )
+        if (shouldPunchHoleBehindScrim) {
+            Spacer(
+                modifier =
+                    Modifier.fillMaxSize().drawBehind {
+                        drawRect(Color.Black, blendMode = BlendMode.DstOut)
+                    }
+            )
+        }
         Box(
             modifier =
                 Modifier.fillMaxSize()
                     .graphicsLayer {
                         alpha =
-                            if (layoutState.isNotificationScrimTransitioning()) {
+                            if (shouldPunchHoleBehindScrim) {
                                 (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
                             } else 1f
                     }
@@ -429,13 +432,6 @@
     )
 }
 
-private fun SceneTransitionLayoutState.isNotificationScrimTransitioning(): Boolean {
-    return isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
-        isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) ||
-        isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings) ||
-        isTransitioningBetween(Scenes.Lockscreen, Scenes.QuickSettings)
-}
-
 private const val TAG = "FlexiNotifs"
 private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
 private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 62619f5..b33ea78 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -61,6 +61,7 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
 import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.compose.modifiers.thenIf
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
@@ -155,15 +156,22 @@
         qsSceneAdapter = viewModel.qsSceneAdapter
     )
 
+    val shouldPunchHoleBehindScrim =
+        layoutState.isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings) ||
+            layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.QuickSettings)
+
     // TODO(b/280887232): implement the real UI.
     Box(
         modifier =
-            modifier.fillMaxSize().graphicsLayer {
-                // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
-                // scene (and not the one under it) during a scene transition.
-                compositingStrategy = CompositingStrategy.Offscreen
-                alpha = contentAlpha
-            }
+            modifier
+                .fillMaxSize()
+                .graphicsLayer { alpha = contentAlpha }
+                .thenIf(shouldPunchHoleBehindScrim) {
+                    // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears
+                    // this
+                    // scene (and not the one under it) during a scene transition.
+                    Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
+                }
     ) {
         val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
         val screenHeight = LocalRawScreenHeight.current
@@ -313,6 +321,7 @@
         NotificationScrollingStack(
             viewModel = notificationsPlaceholderViewModel,
             maxScrimTop = { screenHeight },
+            shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
             modifier =
                 Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) },
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 6bd4cf9..d7b10a9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -16,6 +16,7 @@
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+import com.android.systemui.shade.ui.composable.Shade
 
 /**
  * Comprehensive definition of all transitions between scenes in [SceneContainer].
@@ -80,10 +81,14 @@
 
     // Scene overscroll
 
+    overscroll(Scenes.Gone, Orientation.Vertical) {}
     overscroll(Scenes.Bouncer, Orientation.Vertical) {
         translate(Bouncer.Elements.Content, y = { absoluteDistance })
     }
     overscroll(Scenes.Shade, Orientation.Vertical) {
-        translate(Notifications.Elements.NotificationScrim, y = { absoluteDistance })
+        translate(
+            Notifications.Elements.NotificationScrim,
+            y = { Shade.Dimensions.ScrimOverscrollLimit }
+        )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index a9e5be9..8cd357e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -1,10 +1,15 @@
 package com.android.systemui.scene.ui.composable.transitions
 
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
@@ -12,6 +17,18 @@
     durationScale: Double = 1.0,
 ) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+    distance =
+        object : UserActionDistance {
+            override fun UserActionDistanceScope.absoluteDistance(
+                fromSceneSize: IntSize,
+                orientation: Orientation,
+            ): Float {
+                val distance =
+                    Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y
+                        ?: return 0f
+                return fromSceneSize.height - distance
+            }
+        }
 
     translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
     timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 2f59217..df47cba 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -16,11 +16,19 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ui.composable.Shade
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
@@ -28,6 +36,20 @@
     durationScale: Double = 1.0,
 ) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+    swipeSpec =
+        spring(
+            stiffness = Spring.StiffnessMediumLow,
+            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+        )
+    distance =
+        object : UserActionDistance {
+            override fun UserActionDistanceScope.absoluteDistance(
+                fromSceneSize: IntSize,
+                orientation: Orientation,
+            ): Float {
+                return Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
+            }
+        }
 
     fractionRange(start = .58f) {
         fade(ShadeHeader.Elements.Clock)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
new file mode 100644
index 0000000..d528736
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 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.systemui.shade.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+
+/** The overlay shade renders a lightweight shade UI container on top of a background scene. */
+@Composable
+fun SceneScope.OverlayShade(
+    viewModel: OverlayShadeViewModel,
+    horizontalArrangement: Arrangement.Horizontal,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit,
+) {
+    val backgroundScene by viewModel.backgroundScene.collectAsState()
+
+    Box(modifier) {
+        if (backgroundScene == Scenes.Lockscreen) {
+            Lockscreen()
+        }
+
+        Scrim(onClicked = viewModel::onScrimClicked)
+
+        Row(
+            modifier = Modifier.fillMaxSize().padding(OverlayShade.Dimensions.ScrimContentPadding),
+            horizontalArrangement = horizontalArrangement,
+        ) {
+            Panel(content = content)
+        }
+    }
+}
+
+@Composable
+private fun Lockscreen(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/338025605): This is a placeholder, replace with the actual lockscreen.
+    Box(modifier = modifier.fillMaxSize().background(Color.LightGray)) {
+        Text(text = "Lockscreen", modifier = Modifier.align(Alignment.Center))
+    }
+}
+
+@Composable
+private fun SceneScope.Scrim(
+    onClicked: () -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    Spacer(
+        modifier =
+            modifier
+                .element(OverlayShade.Elements.Scrim)
+                .fillMaxSize()
+                .background(OverlayShade.Colors.ScrimBackground)
+                .clickable(onClick = onClicked, interactionSource = null, indication = null)
+    )
+}
+
+@Composable
+private fun SceneScope.Panel(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit,
+) {
+    Box(
+        modifier =
+            modifier
+                .width(OverlayShade.Dimensions.PanelWidth)
+                .clip(OverlayShade.Shapes.RoundedCornerPanel)
+    ) {
+        Spacer(
+            modifier =
+                Modifier.element(OverlayShade.Elements.PanelBackground)
+                    .matchParentSize()
+                    .background(
+                        color = OverlayShade.Colors.PanelBackground,
+                        shape = OverlayShade.Shapes.RoundedCornerPanel,
+                    ),
+        )
+
+        // This content is intentionally rendered as a separate element from the background in order
+        // to allow for more flexibility when defining transitions.
+        content()
+    }
+}
+
+object OverlayShade {
+    object Elements {
+        val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker)
+        val PanelBackground =
+            ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker)
+    }
+
+    object Colors {
+        val ScrimBackground = Color(0, 0, 0, alpha = 255 / 3)
+        val PanelBackground: Color
+            @Composable @ReadOnlyComposable get() = MaterialTheme.colorScheme.surfaceContainer
+    }
+
+    object Dimensions {
+        val ScrimContentPadding = 16.dp
+        val PanelCornerRadius = 46.dp
+        // TODO(b/338033836): This width should not be fixed.
+        val PanelWidth = 390.dp
+    }
+
+    object Shapes {
+        val RoundedCornerPanel = RoundedCornerShape(Dimensions.PanelCornerRadius)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index cda8059..3122b99 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -98,6 +98,8 @@
     object Dimensions {
         val ScrimCornerSize = 32.dp
         val HorizontalPadding = 16.dp
+        const val ScrimOverscrollLimit = 100f
+        const val ScrimVisibilityThreshold = 5f
     }
 
     object Shapes {
@@ -195,12 +197,26 @@
 ) {
     val maxNotifScrimTop = remember { mutableStateOf(0f) }
     val tileSquishiness by
-        animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
+        animateSceneFloatAsState(
+            value = 1f,
+            key = QuickSettings.SharedValues.TilesSquishiness,
+            canOverflow = false
+        )
     val isClickable by viewModel.isClickable.collectAsState()
 
-    // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this scene
-    // (and not the one under it) during a scene transition.
-    Box(modifier = modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)) {
+    val shouldPunchHoleBehindScrim =
+        layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
+            layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
+
+    Box(
+        modifier =
+            modifier.thenIf(shouldPunchHoleBehindScrim) {
+                // Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
+                // scene
+                // (and not the one under it) during a scene transition.
+                Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
+            }
+    ) {
         Box(
             modifier =
                 Modifier.fillMaxSize()
@@ -231,10 +247,7 @@
                             Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) {
                                 QuickSettings(
                                     viewModel.qsSceneAdapter,
-                                    {
-                                        (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
-                                            .roundToInt()
-                                    },
+                                    { viewModel.qsSceneAdapter.qqsHeight },
                                     isSplitShade = false,
                                     squishiness = tileSquishiness,
                                 )
@@ -254,6 +267,7 @@
                         NotificationScrollingStack(
                             viewModel = viewModel.notifications,
                             maxScrimTop = { maxNotifScrimTop.value },
+                            shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
                         )
                     },
                 )
@@ -425,6 +439,7 @@
                 NotificationScrollingStack(
                     viewModel = viewModel.notifications,
                     maxScrimTop = { 0f },
+                    shouldPunchHoleBehindScrim = false,
                     modifier =
                         Modifier.weight(1f)
                             .fillMaxHeight()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 05dd5cc..0fc0053 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -17,8 +17,6 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
 import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
 import androidx.compose.foundation.gestures.horizontalDrag
@@ -26,12 +24,14 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
 import androidx.compose.ui.input.pointer.positionChange
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.input.pointer.util.addPointerInputChange
@@ -45,8 +45,12 @@
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastForEach
+import kotlin.coroutines.cancellation.CancellationException
 import kotlin.math.sign
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
 
 /**
  * Make an element draggable in the given [orientation].
@@ -163,75 +167,95 @@
             return
         }
 
-        detectDragGestures(
-            orientation = orientation,
-            startDragImmediately = startDragImmediately,
-            onDragStart = { startedPosition, overSlop, pointersDown ->
-                velocityTracker.resetTracking()
-                onDragStarted(startedPosition, overSlop, pointersDown)
-            },
-            onDrag = { controller, change, amount ->
-                velocityTracker.addPointerInputChange(change)
-                controller.onDrag(amount)
-            },
-            onDragEnd = { controller ->
-                val viewConfiguration = currentValueOf(LocalViewConfiguration)
-                val maxVelocity = viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) }
-                val velocity = velocityTracker.calculateVelocity(maxVelocity)
-                controller.onStop(
-                    velocity =
-                        when (orientation) {
-                            Orientation.Horizontal -> velocity.x
-                            Orientation.Vertical -> velocity.y
-                        },
-                    canChangeScene = true,
-                )
-            },
-            onDragCancel = { controller ->
-                controller.onStop(velocity = 0f, canChangeScene = true)
-            },
-        )
+        coroutineScope {
+            awaitPointerEventScope {
+                while (isActive) {
+                    try {
+                        detectDragGestures(
+                            orientation = orientation,
+                            startDragImmediately = startDragImmediately,
+                            onDragStart = { startedPosition, overSlop, pointersDown ->
+                                velocityTracker.resetTracking()
+                                onDragStarted(startedPosition, overSlop, pointersDown)
+                            },
+                            onDrag = { controller, change, amount ->
+                                velocityTracker.addPointerInputChange(change)
+                                controller.onDrag(amount)
+                            },
+                            onDragEnd = { controller ->
+                                val viewConfiguration = currentValueOf(LocalViewConfiguration)
+                                val maxVelocity =
+                                    viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) }
+                                val velocity = velocityTracker.calculateVelocity(maxVelocity)
+                                controller.onStop(
+                                    velocity =
+                                        when (orientation) {
+                                            Orientation.Horizontal -> velocity.x
+                                            Orientation.Vertical -> velocity.y
+                                        },
+                                    canChangeScene = true,
+                                )
+                            },
+                            onDragCancel = { controller ->
+                                controller.onStop(velocity = 0f, canChangeScene = true)
+                            },
+                        )
+                    } catch (exception: CancellationException) {
+                        // If the coroutine scope is active, we can just restart the drag cycle.
+                        if (!isActive) {
+                            throw exception
+                        }
+                    }
+                }
+            }
+        }
     }
-}
 
-/**
- * Detect drag gestures in the given [orientation].
- *
- * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
- * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
- * 1) starting the gesture immediately without requiring a drag >= touch slope;
- * 2) passing the number of pointers down to [onDragStart].
- */
-private suspend fun PointerInputScope.detectDragGestures(
-    orientation: Orientation,
-    startDragImmediately: (startedPosition: Offset) -> Boolean,
-    onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
-    onDragEnd: (controller: DragController) -> Unit,
-    onDragCancel: (controller: DragController) -> Unit,
-    onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit,
-) {
-    awaitEachGesture {
-        val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
+    /**
+     * Detect drag gestures in the given [orientation].
+     *
+     * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
+     * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
+     * 1) starting the gesture immediately without requiring a drag >= touch slope;
+     * 2) passing the number of pointers down to [onDragStart].
+     */
+    private suspend fun AwaitPointerEventScope.detectDragGestures(
+        orientation: Orientation,
+        startDragImmediately: (startedPosition: Offset) -> Boolean,
+        onDragStart:
+            (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+        onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit,
+        onDragEnd: (controller: DragController) -> Unit,
+        onDragCancel: (controller: DragController) -> Unit
+    ) {
+        // Wait for a consumable event in [PointerEventPass.Main] pass
+        val consumablePointer = awaitConsumableEvent().changes.first()
+
         var overSlop = 0f
         val drag =
-            if (startDragImmediately(initialDown.position)) {
-                initialDown.consume()
-                initialDown
+            if (startDragImmediately(consumablePointer.position)) {
+                consumablePointer.consume()
+                consumablePointer
             } else {
-                val down = awaitFirstDown(requireUnconsumed = false)
                 val onSlopReached = { change: PointerInputChange, over: Float ->
                     change.consume()
                     overSlop = over
                 }
 
-                // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once
-                // it is public.
+                // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
+                // is public.
                 val drag =
                     when (orientation) {
                         Orientation.Horizontal ->
-                            awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached)
+                            awaitHorizontalTouchSlopOrCancellation(
+                                consumablePointer.id,
+                                onSlopReached
+                            )
                         Orientation.Vertical ->
-                            awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached)
+                            awaitVerticalTouchSlopOrCancellation(
+                                consumablePointer.id,
+                                onSlopReached
+                            )
                     }
 
                 // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
@@ -240,16 +264,10 @@
                 // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
                 // true).
                 if (drag != null && overSlop == 0f) {
-                    val deltaOffset = drag.position - initialDown.position
-                    val delta =
-                        when (orientation) {
-                            Orientation.Horizontal -> deltaOffset.x
-                            Orientation.Vertical -> deltaOffset.y
-                        }
+                    val delta = (drag.position - consumablePointer.position).toFloat()
                     check(delta != 0f) { "delta is equal to 0" }
                     overSlop = delta.sign
                 }
-
                 drag
             }
 
@@ -272,12 +290,12 @@
                     when (orientation) {
                         Orientation.Horizontal ->
                             horizontalDrag(drag.id) {
-                                onDrag(controller, it, it.positionChange().x)
+                                onDrag(controller, it, it.positionChange().toFloat())
                                 it.consume()
                             }
                         Orientation.Vertical ->
                             verticalDrag(drag.id) {
-                                onDrag(controller, it, it.positionChange().y)
+                                onDrag(controller, it, it.positionChange().toFloat())
                                 it.consume()
                             }
                     }
@@ -293,4 +311,35 @@
             }
         }
     }
+
+    private suspend fun AwaitPointerEventScope.awaitConsumableEvent(): PointerEvent {
+        fun canBeConsumed(changes: List<PointerInputChange>): Boolean {
+            // All pointers must be:
+            return changes.fastAll {
+                // A) recently pressed: even if the event has already been consumed, we can still
+                // use the recently added finger event to determine whether to initiate dragging the
+                // scene.
+                it.changedToDownIgnoreConsumed() ||
+                    // B) unconsumed AND in a new position (on the current axis)
+                    it.positionChange().toFloat() != 0f
+            }
+        }
+
+        var event: PointerEvent
+        do {
+            // To allow the descendants with the opportunity to consume the event, we wait for it in
+            // the Main pass.
+            event = awaitPointerEvent()
+        } while (!canBeConsumed(event.changes))
+
+        // We found a consumable event in the Main pass
+        return event
+    }
+
+    private fun Offset.toFloat(): Float {
+        return when (orientation) {
+            Orientation.Vertical -> y
+            Orientation.Horizontal -> x
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index d8cf1c1..aa6d113 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -17,7 +17,10 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -25,6 +28,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -32,6 +38,8 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -110,4 +118,232 @@
         assertThat(dragged).isTrue()
         assertThat(stopped).isTrue()
     }
+
+    @Test
+    fun handleDisappearingScrollableDuringAGesture() {
+        val size = 200f
+        val middle = Offset(size / 2f, size / 2f)
+
+        var started = false
+        var dragged = false
+        var stopped = false
+        var consumedByScroll = false
+        var hasScrollable by mutableStateOf(true)
+
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(
+                Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+                    .multiPointerDraggable(
+                        orientation = Orientation.Vertical,
+                        enabled = { true },
+                        startDragImmediately = { false },
+                        onDragStarted = { _, _, _ ->
+                            started = true
+                            object : DragController {
+                                override fun onDrag(delta: Float) {
+                                    dragged = true
+                                }
+
+                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
+                                    stopped = true
+                                }
+                            }
+                        },
+                    )
+            ) {
+                if (hasScrollable) {
+                    Box(
+                        Modifier.scrollable(
+                                // Consume all the vertical scroll gestures
+                                rememberScrollableState(
+                                    consumeScrollDelta = {
+                                        consumedByScroll = true
+                                        it
+                                    }
+                                ),
+                                Orientation.Vertical
+                            )
+                            .fillMaxSize()
+                    )
+                }
+            }
+        }
+
+        fun startDraggingDown() {
+            rule.onRoot().performTouchInput {
+                down(middle)
+                moveBy(Offset(0f, touchSlop))
+            }
+        }
+
+        fun continueDraggingDown() {
+            rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
+        }
+
+        fun releaseFinger() {
+            rule.onRoot().performTouchInput { up() }
+        }
+
+        // Swipe down. This should intercepted by the scrollable modifier.
+        startDraggingDown()
+        assertThat(consumedByScroll).isTrue()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+
+        // Reset the scroll state for the test
+        consumedByScroll = false
+
+        // Suddenly remove the scrollable container
+        hasScrollable = false
+        rule.waitForIdle()
+
+        // Swipe down. This will be intercepted by multiPointerDraggable, it will wait touchSlop
+        // before consuming it.
+        continueDraggingDown()
+        assertThat(consumedByScroll).isFalse()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+
+        // Swipe down. This should both call onDragStarted() and onDragDelta().
+        continueDraggingDown()
+        assertThat(consumedByScroll).isFalse()
+        assertThat(started).isTrue()
+        assertThat(dragged).isTrue()
+        assertThat(stopped).isFalse()
+
+        rule.waitForIdle()
+        releaseFinger()
+        assertThat(stopped).isTrue()
+    }
+
+    @Test
+    fun multiPointerWaitAConsumableEventInMainPass() {
+        val size = 200f
+        val middle = Offset(size / 2f, size / 2f)
+
+        var started = false
+        var dragged = false
+        var stopped = false
+
+        var childConsumesOnPass: PointerEventPass? = null
+
+        suspend fun AwaitPointerEventScope.childPointerInputScope() {
+            awaitPointerEvent(PointerEventPass.Initial).also { initial ->
+                // Check unconsumed: it should be always true
+                assertThat(initial.changes.any { it.isConsumed }).isFalse()
+
+                if (childConsumesOnPass == PointerEventPass.Initial) {
+                    initial.changes.first().consume()
+                }
+            }
+
+            awaitPointerEvent(PointerEventPass.Main).also { main ->
+                // Check unconsumed
+                if (childConsumesOnPass != PointerEventPass.Initial) {
+                    assertThat(main.changes.any { it.isConsumed }).isFalse()
+                }
+
+                if (childConsumesOnPass == PointerEventPass.Main) {
+                    main.changes.first().consume()
+                }
+            }
+        }
+
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(
+                Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+                    .multiPointerDraggable(
+                        orientation = Orientation.Vertical,
+                        enabled = { true },
+                        startDragImmediately = { false },
+                        onDragStarted = { _, _, _ ->
+                            started = true
+                            object : DragController {
+                                override fun onDrag(delta: Float) {
+                                    dragged = true
+                                }
+
+                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
+                                    stopped = true
+                                }
+                            }
+                        },
+                    )
+            ) {
+                Box(
+                    Modifier.pointerInput(Unit) {
+                            coroutineScope {
+                                awaitPointerEventScope {
+                                    while (isActive) {
+                                        childPointerInputScope()
+                                    }
+                                }
+                            }
+                        }
+                        .fillMaxSize()
+                )
+            }
+        }
+
+        fun startDraggingDown() {
+            rule.onRoot().performTouchInput {
+                down(middle)
+                moveBy(Offset(0f, touchSlop))
+            }
+        }
+
+        fun continueDraggingDown() {
+            rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
+        }
+
+        childConsumesOnPass = PointerEventPass.Initial
+
+        startDraggingDown()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+
+        continueDraggingDown()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+
+        childConsumesOnPass = PointerEventPass.Main
+
+        continueDraggingDown()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+
+        continueDraggingDown()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+
+        childConsumesOnPass = null
+
+        // Swipe down. This will be intercepted by multiPointerDraggable, it will wait touchSlop
+        // before consuming it.
+        continueDraggingDown()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+
+        // Swipe down. This should both call onDragStarted() and onDragDelta().
+        continueDraggingDown()
+        assertThat(started).isTrue()
+        assertThat(dragged).isTrue()
+        assertThat(stopped).isFalse()
+
+        childConsumesOnPass = PointerEventPass.Main
+
+        continueDraggingDown()
+        assertThat(stopped).isTrue()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 3395268..630bcd6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.animation.AnimatorListenerAdapter;
@@ -163,6 +164,7 @@
      * Ensures expansion only happens when touch down happens in valid part of the screen.
      */
     @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
     public void testSessionStart() {
         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
 
@@ -193,7 +195,31 @@
                         2)).isTrue();
     }
 
+
+    /**
+     * Ensures expansion only happens when touch down happens in valid part of the screen.
+     */
     @Test
+    @EnableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
+    public void testSessionStart_doesNotModifyNotificationShadeWindow() {
+        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
+
+        verify(mRegion).union(mRectCaptor.capture());
+        final Rect bounds = mRectCaptor.getValue();
+
+        final Rect expected = new Rect();
+
+        expected.set(0, Math.round(SCREEN_HEIGHT_PX * (1 - TOUCH_REGION)), SCREEN_WIDTH_PX,
+                SCREEN_HEIGHT_PX);
+
+        assertThat(bounds).isEqualTo(expected);
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verifyNoMoreInteractions(mNotificationShadeWindowController);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
     public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() {
         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
                 new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX));
@@ -213,6 +239,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
     public void testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea() {
         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
                 new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX / 4));
@@ -228,6 +255,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
     public void testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea() {
         final int normalSwipeAreaTop = SCREEN_HEIGHT_PX
                 - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
index 88ad3f3..3005d0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
@@ -70,8 +70,10 @@
     fun faceUnlocked_showsAuthRipple() =
         testScope.runTest {
             val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
-            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
-            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            keyguardRepository.setBiometricUnlockState(
+                BiometricUnlockMode.WAKE_AND_UNLOCK,
+                BiometricUnlockSource.FACE_SENSOR
+            )
             assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
         }
 
@@ -79,8 +81,10 @@
     fun fingerprintUnlocked_showsAuthRipple() =
         testScope.runTest {
             val showUnlockRippleFromBiometricUnlock by collectLastValue(underTest.showUnlockRipple)
-            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
-            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            keyguardRepository.setBiometricUnlockState(
+                BiometricUnlockMode.WAKE_AND_UNLOCK,
+                BiometricUnlockSource.FINGERPRINT_SENSOR
+            )
             assertThat(showUnlockRippleFromBiometricUnlock)
                 .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
index d216fa0..2e4c97b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
@@ -46,8 +46,10 @@
         testScope.runTest {
             val deviceEntryFromBiometricAuthentication by
                 collectLastValue(underTest.deviceEntryFromBiometricSource)
-            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
-            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            keyguardRepository.setBiometricUnlockState(
+                BiometricUnlockMode.WAKE_AND_UNLOCK,
+                BiometricUnlockSource.FACE_SENSOR,
+            )
             runCurrent()
             assertThat(deviceEntryFromBiometricAuthentication)
                 .isEqualTo(BiometricUnlockSource.FACE_SENSOR)
@@ -57,8 +59,10 @@
     fun deviceEntryFromFingerprintUnlock() = runTest {
         val deviceEntryFromBiometricAuthentication by
             collectLastValue(underTest.deviceEntryFromBiometricSource)
-        keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
-        keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        keyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.WAKE_AND_UNLOCK,
+            BiometricUnlockSource.FINGERPRINT_SENSOR,
+        )
         runCurrent()
         assertThat(deviceEntryFromBiometricAuthentication)
             .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
@@ -68,9 +72,10 @@
     fun noDeviceEntry() = runTest {
         val deviceEntryFromBiometricAuthentication by
             collectLastValue(underTest.deviceEntryFromBiometricSource)
-        keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
-        // doesn't dismiss keyguard:
-        keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.ONLY_WAKE)
+        keyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.ONLY_WAKE, // doesn't dismiss keyguard:
+            BiometricUnlockSource.FINGERPRINT_SENSOR,
+        )
         runCurrent()
         assertThat(deviceEntryFromBiometricAuthentication).isNull()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 19b80da..f375ec7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -515,11 +516,9 @@
     fun biometricUnlockSource() =
         testScope.runTest {
             val values = mutableListOf<BiometricUnlockSource?>()
-            val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
+            val job = underTest.biometricUnlockState.onEach { values.add(it.source) }.launchIn(this)
 
             runCurrent()
-            val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-            verify(keyguardUpdateMonitor).registerCallback(captor.capture())
 
             // An initial, null value should be initially emitted so that flows combined with this
             // one
@@ -535,7 +534,10 @@
                     BiometricSourceType.FINGERPRINT,
                 )
                 .onEach { biometricSourceType ->
-                    captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
+                    underTest.setBiometricUnlockState(
+                        BiometricUnlockMode.NONE,
+                        BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+                    )
                     runCurrent()
                 }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index 69a1a0f..0bfcd54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.data.repository.powerRepository
@@ -89,7 +89,10 @@
 
         // The source and sensor locations are still null, so we should still be using the
         // default reveal despite a biometric unlock.
-        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.WAKE_AND_UNLOCK,
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
 
         runCurrent()
         values.assertEffectsMatchPredicates(
@@ -98,7 +101,10 @@
 
         // We got a source but still have no sensor locations, so should be sticking with
         // the default effect.
-        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.WAKE_AND_UNLOCK,
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
 
         runCurrent()
         values.assertEffectsMatchPredicates(
@@ -117,8 +123,10 @@
         // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
         val fingerprintLocation = Point(500, 500)
         fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
-        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
-        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING,
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
 
         // We should now have switched to the circle reveal, at the fingerprint location.
         runCurrent()
@@ -133,14 +141,21 @@
 
         // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
         val valuesPrevSize = values.size
-        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
         fakeKeyguardRepository.setBiometricUnlockState(
-            BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING,
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM,
+            BiometricUnlockSource.FINGERPRINT_SENSOR
         )
         assertEquals(valuesPrevSize, values.size)
 
         // Non-biometric unlock, we should return to the default reveal.
-        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.NONE,
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
 
         runCurrent()
         values.assertEffectsMatchPredicates(
@@ -155,9 +170,10 @@
 
         // We already have a face location, so switching to face source should update the
         // CircleReveal.
-        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
-        runCurrent()
-        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockMode.WAKE_AND_UNLOCK,
+            BiometricUnlockSource.FACE_SENSOR
+        )
         runCurrent()
 
         values.assertEffectsMatchPredicates(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 360f284..d630a2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -43,8 +43,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -99,7 +98,7 @@
                 to = KeyguardState.AOD,
                 testScope
             )
-            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+            kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
             reset(transitionRepository)
         }
     }
@@ -278,7 +277,9 @@
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testWakeAndUnlock_transitionsToGone_onlyAfterDismissCallPostWakeup() =
         testScope.runTest {
-            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            kosmos.fakeKeyguardRepository.setBiometricUnlockState(
+                BiometricUnlockMode.WAKE_AND_UNLOCK
+            )
             powerInteractor.setAwakeForTest()
             runCurrent()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 12f8918..323087c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -24,23 +24,23 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,33 +60,17 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val repository by lazy { kosmos.fakeKeyguardRepository }
-    private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
-    private val commandQueue by lazy { FakeCommandQueue() }
-    private val bouncerRepository = FakeKeyguardBouncerRepository()
-    private val shadeRepository = FakeShadeRepository()
+    private val repository = kosmos.fakeKeyguardRepository
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
+    private val commandQueue = kosmos.fakeCommandQueue
+    private val configRepository = kosmos.fakeConfigurationRepository
+    private val bouncerRepository = kosmos.keyguardBouncerRepository
+    private val shadeRepository = kosmos.shadeRepository
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val transitionState: MutableStateFlow<ObservableTransitionState> =
         MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
-
-    private val underTest by lazy {
-        KeyguardInteractor(
-            repository = repository,
-            commandQueue = commandQueue,
-            powerInteractor = PowerInteractorFactory.create().powerInteractor,
-            bouncerRepository = bouncerRepository,
-            configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
-            shadeRepository = shadeRepository,
-            keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
-            sceneInteractorProvider = { sceneInteractor },
-            fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
-            sharedNotificationContainerInteractor = {
-                kosmos.sharedNotificationContainerInteractor
-            },
-            applicationScope = testScope,
-        )
-    }
+    private val underTest = kosmos.keyguardInteractor
 
     @Before
     fun setUp() {
@@ -247,6 +231,84 @@
         }
 
     @Test
+    fun keyguardTranslationY_whenGoneEmitsZero() =
+        testScope.runTest {
+            val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+            configRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                100
+            )
+            configRepository.onAnyConfigurationChange()
+
+            shadeRepository.setLegacyShadeExpansion(0f)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            assertThat(keyguardTranslationY).isEqualTo(0f)
+        }
+
+    @Test
+    fun keyguardTranslationY_whenNotGoneAndShadeIsFullyCollapsedEmitsZero() =
+        testScope.runTest {
+            val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+            configRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                100
+            )
+            configRepository.onAnyConfigurationChange()
+
+            shadeRepository.setLegacyShadeExpansion(0f)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            assertThat(keyguardTranslationY).isEqualTo(0f)
+        }
+
+    @Test
+    fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() =
+        testScope.runTest {
+            val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+            configRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                100
+            )
+            configRepository.onAnyConfigurationChange()
+
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    TransitionStep(
+                        from = KeyguardState.AOD,
+                        to = KeyguardState.GONE,
+                        value = 0f,
+                        transitionState = TransitionState.STARTED,
+                    ),
+                    TransitionStep(
+                        from = KeyguardState.AOD,
+                        to = KeyguardState.GONE,
+                        value = 0.1f,
+                        transitionState = TransitionState.RUNNING,
+                    ),
+                ),
+                testScope,
+            )
+
+            assertThat(keyguardTranslationY).isGreaterThan(0f)
+        }
+
+    @Test
     @EnableSceneContainer
     fun animationDozingTransitions() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index 9c0674d..bc57ce6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -245,6 +245,33 @@
             assertThat(flowForUser2).isNotEqualTo(flowForUser1)
         }
 
+    // Tests that a ServiceInfo that is returned by queryIntentServicesAsUser but shortly
+    // after uninstalled, doesn't crash SystemUI.
+    @Test
+    fun packageUninstalledAfterQuery_noCrash_noComponent() =
+        testScope.runTest {
+            val userId = 0
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+            whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+                .thenThrow(IllegalArgumentException())
+            kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
+            runCurrent()
+
+            assertThat(componentNames).isEmpty()
+        }
+
     companion object {
         private val INTENT = Intent(TileService.ACTION_QS_TILE)
         private val FLAGS =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
new file mode 100644
index 0000000..0ffabd8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 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.systemui.shade.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class OverlayShadeViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+
+    private val underTest = kosmos.overlayShadeViewModel
+
+    @Test
+    fun backgroundScene_deviceLocked_lockscreen() =
+        testScope.runTest {
+            val backgroundScene by collectLastValue(underTest.backgroundScene)
+
+            lockDevice()
+
+            assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun backgroundScene_deviceUnlocked_gone() =
+        testScope.runTest {
+            val backgroundScene by collectLastValue(underTest.backgroundScene)
+
+            lockDevice()
+            unlockDevice()
+
+            assertThat(backgroundScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun backgroundScene_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val backgroundScene by collectLastValue(underTest.backgroundScene)
+            val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+            runCurrent()
+
+            assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun backgroundScene_authMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val backgroundScene by collectLastValue(underTest.backgroundScene)
+            val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+            sceneInteractor.changeScene(Scenes.Gone, "reason")
+            runCurrent()
+
+            assertThat(backgroundScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun onScrimClicked_onLockscreen_goesToLockscreen() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            lockDevice()
+            sceneInteractor.changeScene(Scenes.Bouncer, "reason")
+            runCurrent()
+            assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen)
+
+            underTest.onScrimClicked()
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+        }
+
+    @Test
+    fun onScrimClicked_deviceWasEntered_goesToGone() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val backgroundScene by collectLastValue(underTest.backgroundScene)
+
+            lockDevice()
+            unlockDevice()
+            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+            runCurrent()
+            assertThat(backgroundScene).isEqualTo(Scenes.Gone)
+            assertThat(currentScene).isNotEqualTo(Scenes.Gone)
+
+            underTest.onScrimClicked()
+
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+        }
+
+    private fun TestScope.lockDevice() {
+        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+        assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+        sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+        runCurrent()
+    }
+
+    private fun TestScope.unlockDevice() {
+        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+
+        kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+            SuccessFingerprintAuthenticationStatus(0, true)
+        )
+        assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+        sceneInteractor.changeScene(Scenes.Gone, "reason")
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 1501d9c..b643968 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -30,7 +30,6 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.mock
-import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index b443489..0ca6207 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -259,7 +259,7 @@
         underTest.dismissKeyguardThenExecute({ true }, {}, false)
 
         verify(biometricUnlockController)
-            .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+            .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, null)
     }
 
     @Test
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 6e1b670..660f0db 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -68,6 +68,7 @@
 import java.io.PrintWriter;
 import java.util.Optional;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.function.Supplier;
 
 /**
@@ -199,6 +200,10 @@
         return mContext;
     }
 
+    /**
+     * We should pass single threaded executor (rather than {@link ThreadPoolExecutor}) as we will
+     * make binder calls on that executor and ordering is vital.
+     */
     public void setBgExecutor(Executor bgExecutor) {
         mBgExecutor = bgExecutor;
     }
@@ -230,25 +235,22 @@
         mListenersRegistered = true;
 
         mBgExecutor.execute(() -> {
+            if (registerRotationWatcher) {
+                try {
+                    WindowManagerGlobal.getWindowManagerService()
+                            .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
+                    mRotationWatcherRegistered = true;
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG, "RegisterListeners for the display failed", e);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RegisterListeners caught a RemoteException", e);
+                }
+            }
             final Intent intent = mContext.registerReceiver(mDockedReceiver,
                     new IntentFilter(Intent.ACTION_DOCK_EVENT));
             mContext.getMainExecutor().execute(() -> updateDockedState(intent));
         });
 
-        if (registerRotationWatcher) {
-            try {
-                WindowManagerGlobal.getWindowManagerService()
-                        .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
-                mRotationWatcherRegistered = true;
-            } catch (IllegalArgumentException e) {
-                mListenersRegistered = false;
-                Log.w(TAG, "RegisterListeners for the display failed", e);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RegisterListeners caught a RemoteException", e);
-                return;
-            }
-        }
-
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
     }
 
@@ -265,17 +267,16 @@
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Docked receiver already unregistered", e);
             }
-        });
 
-        if (mRotationWatcherRegistered) {
-            try {
-                WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(
-                        mRotationWatcher);
-            } catch (RemoteException e) {
-                Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
-                return;
+            if (mRotationWatcherRegistered) {
+                try {
+                    WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(
+                            mRotationWatcher);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
+                }
             }
-        }
+        });
 
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
index 85aeb27..019f498 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
@@ -254,7 +254,11 @@
         mVelocityTracker = mVelocityTrackerFactory.obtain();
         mTouchSession = session;
         mVelocityTracker.clear();
-        mNotificationShadeWindowController.setForcePluginOpen(true, this);
+
+        if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+            mNotificationShadeWindowController.setForcePluginOpen(true, this);
+        }
+
         mScrimManager.addCallback(mScrimManagerCallback);
         mCurrentScrimController = mScrimManager.getCurrentController();
 
@@ -265,7 +269,10 @@
             }
             mScrimManager.removeCallback(mScrimManagerCallback);
             mCapture = null;
-            mNotificationShadeWindowController.setForcePluginOpen(false, this);
+
+            if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+                mNotificationShadeWindowController.setForcePluginOpen(false, this);
+            }
         });
 
         session.registerGestureListener(mOnGestureListener);
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
index d4f76a8..0b9336f 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -46,10 +46,9 @@
 ) {
     val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> =
         keyguardInteractor.biometricUnlockState
-            .filter { BiometricUnlockModel.dismissesKeyguard(it) }
-            .sample(
-                keyguardInteractor.biometricUnlockSource.filterNotNull(),
-            )
+            .filter { BiometricUnlockMode.dismissesKeyguard(it.mode) }
+            .map { it.source }
+            .filterNotNull()
 
     private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
     val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index db2ec8f..ea8d7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -189,7 +189,6 @@
                 durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
                 effectDuration
             )
-        _postedActionType.value = ActionType.INITIALIZE_ANIMATOR
         setState(State.IDLE)
         return true
     }
@@ -209,6 +208,5 @@
         START_ANIMATOR,
         REVERSE_ANIMATOR,
         CANCEL_ANIMATOR,
-        INITIALIZE_ANIMATOR,
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index c591af2..c464ed1 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -57,6 +57,7 @@
                                     qsLongPressEffect.clearActionType()
                                 }
                                 QSLongPressEffect.ActionType.LONG_PRESS -> {
+                                    tile.prepareForLaunch()
                                     tile.performLongClick()
                                     qsLongPressEffect.clearActionType()
                                 }
@@ -66,8 +67,32 @@
                                     qsLongPressEffect.clearActionType()
                                 }
                                 QSLongPressEffect.ActionType.START_ANIMATOR -> {
-                                    if (effectAnimator?.isRunning == false) {
-                                        effectAnimator?.start()
+                                    if (effectAnimator?.isRunning != true) {
+                                        effectAnimator =
+                                            ValueAnimator.ofFloat(0f, 1f).apply {
+                                                this.duration =
+                                                    qsLongPressEffect.effectDuration.toLong()
+                                                interpolator = AccelerateDecelerateInterpolator()
+
+                                                doOnStart {
+                                                    qsLongPressEffect.handleAnimationStart()
+                                                }
+                                                addUpdateListener {
+                                                    val value = animatedValue as Float
+                                                    if (value == 0f) {
+                                                        tile.bringToFront()
+                                                    } else {
+                                                        tile.updateLongPressEffectProperties(value)
+                                                    }
+                                                }
+                                                doOnEnd {
+                                                    qsLongPressEffect.handleAnimationComplete()
+                                                }
+                                                doOnCancel {
+                                                    qsLongPressEffect.handleAnimationCancel()
+                                                }
+                                                start()
+                                            }
                                     }
                                 }
                                 QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> {
@@ -81,26 +106,6 @@
                                     tile.resetLongPressEffectProperties()
                                     effectAnimator?.cancel()
                                 }
-                                QSLongPressEffect.ActionType.INITIALIZE_ANIMATOR -> {
-                                    effectAnimator =
-                                        ValueAnimator.ofFloat(0f, 1f).apply {
-                                            this.duration =
-                                                qsLongPressEffect.effectDuration.toLong()
-                                            interpolator = AccelerateDecelerateInterpolator()
-
-                                            doOnStart { qsLongPressEffect.handleAnimationStart() }
-                                            addUpdateListener {
-                                                val value = animatedValue as Float
-                                                if (value == 0f) {
-                                                    tile.bringToFront()
-                                                } else {
-                                                    tile.updateLongPressEffectProperties(value)
-                                                }
-                                            }
-                                            doOnEnd { qsLongPressEffect.handleAnimationComplete() }
-                                            doOnCancel { qsLongPressEffect.handleAnimationCancel() }
-                                        }
-                                }
                             }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5d31d1e..6ef39f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3214,6 +3214,10 @@
         mHideAnimationRun = false;
         adjustStatusBarLocked();
         sendUserPresentBroadcast();
+
+        if (!KeyguardWmStateRefactor.isEnabled()) {
+            mKeyguardInteractor.dismissKeyguard();
+        }
     }
 
     private Configuration.Builder createInteractionJankMonitorConf(int cuj) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 462d837..8a53dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
-import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.biometrics.AuthController
@@ -31,6 +30,7 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DismissAction
@@ -169,10 +169,13 @@
     /** Observable for the [StatusBarState] */
     val statusBarState: StateFlow<StatusBarState>
 
-    /** Observable for biometric unlock modes */
+    /** Observable for biometric unlock state which includes the mode and unlock source */
     val biometricUnlockState: Flow<BiometricUnlockModel>
 
-    fun setBiometricUnlockState(value: BiometricUnlockModel)
+    fun setBiometricUnlockState(
+        unlockMode: BiometricUnlockMode,
+        unlockSource: BiometricUnlockSource?,
+    )
 
     /** Approximate location on the screen of the fingerprint sensor. */
     val fingerprintSensorLocation: Flow<Point?>
@@ -180,9 +183,6 @@
     /** Approximate location on the screen of the face unlock sensor/front facing camera. */
     val faceSensorLocation: Flow<Point?>
 
-    /** Source of the most recent biometric unlock, such as fingerprint or face. */
-    val biometricUnlockSource: Flow<BiometricUnlockSource?>
-
     /** Whether quick settings or quick-quick settings is visible. */
     val isQuickSettingsVisible: Flow<Boolean>
 
@@ -597,11 +597,15 @@
                 statusBarStateIntToObject(statusBarStateController.state)
             )
 
-    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    private val _biometricUnlockState: MutableStateFlow<BiometricUnlockModel> =
+        MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null))
     override val biometricUnlockState = _biometricUnlockState.asStateFlow()
 
-    override fun setBiometricUnlockState(value: BiometricUnlockModel) {
-        _biometricUnlockState.value = value
+    override fun setBiometricUnlockState(
+        unlockMode: BiometricUnlockMode,
+        unlockSource: BiometricUnlockSource?,
+    ) {
+        _biometricUnlockState.value = BiometricUnlockModel(unlockMode, unlockSource)
     }
 
     override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
@@ -628,27 +632,6 @@
 
     override val faceSensorLocation: Flow<Point?> = facePropertyRepository.sensorLocation
 
-    override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
-        val callback =
-            object : KeyguardUpdateMonitorCallback() {
-                override fun onBiometricAuthenticated(
-                    userId: Int,
-                    biometricSourceType: BiometricSourceType?,
-                    isStrongBiometric: Boolean
-                ) {
-                    trySendWithFailureLogging(
-                        BiometricUnlockSource.fromBiometricSourceType(biometricSourceType),
-                        TAG,
-                        "onBiometricAuthenticated"
-                    )
-                }
-            }
-
-        keyguardUpdateMonitor.registerCallback(callback)
-        trySendWithFailureLogging(null, TAG, "initial value")
-        awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
-    }
-
     private val _isQuickSettingsVisible = MutableStateFlow(false)
     override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 6138330..3956901 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -76,4 +76,9 @@
     @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
 
     @Binds fun keyguardClockRepository(impl: KeyguardClockRepositoryImpl): KeyguardClockRepository
+
+    @Binds
+    fun keyguardSmartspaceRepository(
+        impl: KeyguardSmartspaceRepositoryImpl
+    ): KeyguardSmartspaceRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
index afe9151..956125c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -16,19 +16,68 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.content.Context
+import android.provider.Settings
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface KeyguardSmartspaceRepository {
+    val bcSmartspaceVisibility: StateFlow<Int>
+    val isWeatherEnabled: StateFlow<Boolean>
+    fun setBcSmartspaceVisibility(visibility: Int)
+}
 
 @SysUISingleton
-class KeyguardSmartspaceRepository @Inject constructor() {
+class KeyguardSmartspaceRepositoryImpl
+@Inject
+constructor(
+    context: Context,
+    private val secureSettings: SecureSettings,
+    private val userTracker: UserTracker,
+    @Application private val applicationScope: CoroutineScope,
+) : KeyguardSmartspaceRepository {
     private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE)
-    val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+    override val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+    val defaultValue =
+        context.resources.getBoolean(
+            com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault
+        )
+    override val isWeatherEnabled: StateFlow<Boolean> =
+        secureSettings
+            .observerFlow(
+                names = arrayOf(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED),
+                userId = userTracker.userId,
+            )
+            .onStart { emit(Unit) }
+            .map { getLockscreenWeatherEnabled() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = getLockscreenWeatherEnabled()
+            )
 
-    fun setBcSmartspaceVisibility(visibility: Int) {
+    override fun setBcSmartspaceVisibility(visibility: Int) {
         _bcSmartspaceVisibility.value = visibility
     }
+
+    private fun getLockscreenWeatherEnabled(): Boolean {
+        return secureSettings.getIntForUser(
+            Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
+            if (defaultValue) 1 else 0,
+            userTracker.userId
+        ) == 1
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index eac476f..0a15bbf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -24,7 +24,7 @@
 import androidx.core.animation.ValueAnimator
 import com.android.keyguard.logging.ScrimLogger
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.power.data.repository.PowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
@@ -40,7 +40,6 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -83,7 +82,7 @@
     }
 
     /** The reveal effect used if the device was locked/unlocked via the power button. */
-    private val powerButtonRevealEffect: Flow<LightRevealEffect?> =
+    private val powerButtonRevealEffect: Flow<LightRevealEffect> =
         flowOf(
             PowerButtonReveal(
                 context.resources
@@ -92,42 +91,31 @@
             )
         )
 
-    private val tapRevealEffect: Flow<LightRevealEffect?> =
+    private val tapRevealEffect: Flow<LightRevealEffect> =
         keyguardRepository.lastDozeTapToWakePosition.map {
-            it?.let { constructCircleRevealFromPoint(it) }
+            it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT
         }
 
     /**
      * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint
      * sensor location on the screen (in pixels) changes due to configuration changes.
      */
-    private val fingerprintRevealEffect: Flow<LightRevealEffect?> =
+    private val fingerprintRevealEffect: Flow<LightRevealEffect> =
         keyguardRepository.fingerprintSensorLocation.map {
-            it?.let { constructCircleRevealFromPoint(it) }
+            it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT
         }
 
     /**
      * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera
      * location on the screen (in pixels) changes due to configuration changes.
      */
-    private val faceRevealEffect: Flow<LightRevealEffect?> =
-        keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } }
-
-    /**
-     * The reveal effect we'll use for the next biometric unlock animation. We switch between the
-     * fingerprint/face unlock effect flows depending on the biometric unlock source.
-     */
-    private val biometricRevealEffect: Flow<LightRevealEffect?> =
-        keyguardRepository.biometricUnlockSource.flatMapLatest { source ->
-            when (source) {
-                BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect
-                BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect
-                else -> flowOf(null)
-            }
+    private val faceRevealEffect: Flow<LightRevealEffect> =
+        keyguardRepository.faceSensorLocation.map {
+            it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT
         }
 
     /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
-    private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
+    private val nonBiometricRevealEffect: Flow<LightRevealEffect> =
         powerRepository.wakefulness.flatMapLatest { wakefulnessModel ->
             when {
                 wakefulnessModel.isAwakeOrAsleepFrom(WakeSleepReason.POWER_BUTTON) ->
@@ -169,29 +157,22 @@
         scrimLogger.d(TAG, "startRevealAmountAnimator, reveal", reveal)
     }
 
-    override val revealEffect =
-        combine(
-                keyguardRepository.biometricUnlockState,
-                biometricRevealEffect,
-                nonBiometricRevealEffect
-            ) { biometricUnlockState, biometricReveal, nonBiometricReveal ->
-
+    override val revealEffect: Flow<LightRevealEffect> =
+        keyguardRepository.biometricUnlockState
+            .flatMapLatest { biometricUnlockState ->
                 // Use the biometric reveal for any flavor of wake and unlocking.
-                val revealEffect =
-                    when (biometricUnlockState) {
-                        BiometricUnlockModel.WAKE_AND_UNLOCK,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
-                        BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal
-                        else -> nonBiometricReveal
+                when (biometricUnlockState.mode) {
+                    BiometricUnlockMode.WAKE_AND_UNLOCK,
+                    BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING,
+                    BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM -> {
+                        if (biometricUnlockState.source == BiometricUnlockSource.FACE_SENSOR) {
+                            faceRevealEffect
+                        } else {
+                            fingerprintRevealEffect
+                        }
                     }
-                        ?: DEFAULT_REVEAL_EFFECT
-
-                scrimLogger.d(
-                    TAG,
-                    "revealEffect",
-                    "$revealEffect, biometricUnlockState: ${biometricUnlockState.name}"
-                )
-                return@combine revealEffect
+                    else -> nonBiometricRevealEffect
+                }
             }
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
index cb003a7..576fafd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
@@ -2,7 +2,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
@@ -13,7 +14,9 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
 import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class BiometricUnlockInteractor
 @Inject
@@ -21,21 +24,24 @@
     private val keyguardRepository: KeyguardRepository,
 ) {
 
-    fun setBiometricUnlockState(@WakeAndUnlockMode unlockStateInt: Int) {
+    fun setBiometricUnlockState(
+        @WakeAndUnlockMode unlockStateInt: Int,
+        biometricUnlockSource: BiometricUnlockSource?,
+    ) {
         val state = biometricModeIntToObject(unlockStateInt)
-        keyguardRepository.setBiometricUnlockState(state)
+        keyguardRepository.setBiometricUnlockState(state, biometricUnlockSource)
     }
 
-    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockMode {
         return when (value) {
-            MODE_NONE -> BiometricUnlockModel.NONE
-            MODE_WAKE_AND_UNLOCK -> BiometricUnlockModel.WAKE_AND_UNLOCK
-            MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
-            MODE_SHOW_BOUNCER -> BiometricUnlockModel.SHOW_BOUNCER
-            MODE_ONLY_WAKE -> BiometricUnlockModel.ONLY_WAKE
-            MODE_UNLOCK_COLLAPSING -> BiometricUnlockModel.UNLOCK_COLLAPSING
-            MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
-            MODE_DISMISS_BOUNCER -> BiometricUnlockModel.DISMISS_BOUNCER
+            MODE_NONE -> BiometricUnlockMode.NONE
+            MODE_WAKE_AND_UNLOCK -> BiometricUnlockMode.WAKE_AND_UNLOCK
+            MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING
+            MODE_SHOW_BOUNCER -> BiometricUnlockMode.SHOW_BOUNCER
+            MODE_ONLY_WAKE -> BiometricUnlockMode.ONLY_WAKE
+            MODE_UNLOCK_COLLAPSING -> BiometricUnlockMode.UNLOCK_COLLAPSING
+            MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM
+            MODE_DISMISS_BOUNCER -> BiometricUnlockMode.DISMISS_BOUNCER
             else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index c2843d8..4d73774 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -72,7 +72,8 @@
             keyguardInteractor.isKeyguardDismissible,
             keyguardInteractor.biometricUnlockState,
         ) { isKeyguardShowing, isKeyguardDismissible, biometricUnlockState ->
-            (isWakeAndUnlock(biometricUnlockState) || (!isKeyguardShowing && isKeyguardDismissible))
+            (isWakeAndUnlock(biometricUnlockState.mode) ||
+                (!isKeyguardShowing && isKeyguardDismissible))
         }
 
     /**
@@ -110,11 +111,11 @@
                                 // receiving a call to #dismissAod() shortly when the authentication
                                 // completes.
                                 !maybeStartTransitionToOccludedOrInsecureCamera() &&
-                                    !isWakeAndUnlock(biometricUnlockState) &&
+                                    !isWakeAndUnlock(biometricUnlockState.mode) &&
                                     !primaryBouncerShowing
                             } else {
                                 !isKeyguardOccludedLegacy &&
-                                    !isWakeAndUnlock(biometricUnlockState) &&
+                                    !isWakeAndUnlock(biometricUnlockState.mode) &&
                                     !primaryBouncerShowing
                             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ca7fc66..2eeb3b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
@@ -99,7 +99,7 @@
                         canDismissLockScreen,
                         primaryBouncerShowing) ->
                     startTransitionTo(
-                        if (isWakeAndUnlock(biometricUnlockState)) {
+                        if (isWakeAndUnlock(biometricUnlockState.mode)) {
                             KeyguardState.GONE
                         } else if (canDismissLockScreen) {
                             KeyguardState.GONE
@@ -142,7 +142,7 @@
                     if (
                         !maybeStartTransitionToOccludedOrInsecureCamera() &&
                             // Handled by dismissFromDozing().
-                            !isWakeAndUnlock(biometricUnlockState)
+                            !isWakeAndUnlock(biometricUnlockState.mode)
                     ) {
                         startTransitionTo(
                             if (canDismissLockscreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index 10d1e15..e738ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -104,7 +104,7 @@
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .filterRelevantKeyguardStateAnd { biometricUnlockState ->
-                    biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+                    biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM
                 }
                 .collect { startTransitionTo(KeyguardState.GONE) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 69c2c78d..c952e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -193,7 +193,7 @@
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .filterRelevantKeyguardStateAnd { biometricUnlockState ->
-                    biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+                    biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM
                 }
                 .collect { startTransitionTo(KeyguardState.GONE) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index d191768..88367f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
@@ -90,6 +89,7 @@
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     sceneInteractorProvider: Provider<SceneInteractor>,
     private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
+    private val fromLockscreenTransitionInteractor: Provider<FromLockscreenTransitionInteractor>,
     sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>,
     @Application applicationScope: CoroutineScope,
 ) {
@@ -234,13 +234,7 @@
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
 
-    /** Source of the most recent biometric unlock, such as fingerprint or face. */
-    val biometricUnlockSource: Flow<BiometricUnlockSource?> = repository.biometricUnlockSource
-
-    /**
-     * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
-     * side, under display) is used to unlock the device.
-     */
+    /** Observable for [BiometricUnlockModel] when biometrics are used to unlock the device. */
     val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
 
     /** Keyguard is present and is not occluded. */
@@ -307,19 +301,27 @@
         configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
             .flatMapLatest { translationDistance ->
-                combine(
+                combineTransform(
                     shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
                     keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
                 ) { legacyShadeExpansion, goneValue ->
-                    if (goneValue == 1f || legacyShadeExpansion == 0f) {
+                    if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) {
                         // Reset the translation value
-                        0f
-                    } else {
-                        // On swipe up, translate the keyguard to reveal the bouncer
-                        MathUtils.lerp(
-                            translationDistance,
-                            0,
-                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion)
+                        emit(0f)
+                    } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) {
+                        // On swipe up, translate the keyguard to reveal the bouncer, OR a GONE
+                        // transition is running, which means this is a swipe to dismiss. Values of
+                        // 0f and 1f need to be ignored in the legacy shade expansion. These can
+                        // flip arbitrarily as the legacy shade is reset, and would cause the
+                        // translation value to jump around unexpectedly.
+                        emit(
+                            MathUtils.lerp(
+                                translationDistance,
+                                0,
+                                Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(
+                                    legacyShadeExpansion
+                                ),
+                            )
                         )
                     }
                 }
@@ -417,6 +419,11 @@
         fromGoneTransitionInteractor.get().showKeyguard()
     }
 
+    /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
+    fun dismissKeyguard() {
+        fromLockscreenTransitionInteractor.get().dismissKeyguard()
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
index bb633b5..45a4b70a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
@@ -28,6 +28,7 @@
     private val keyguardSmartspaceRepository: KeyguardSmartspaceRepository,
 ) {
     val bcSmartspaceVisibility: StateFlow<Int> = keyguardSmartspaceRepository.bcSmartspaceVisibility
+    val isWeatherEnabled: StateFlow<Boolean> = keyguardSmartspaceRepository.isWeatherEnabled
 
     fun setBcSmartspaceVisibility(visibility: Int) {
         keyguardSmartspaceRepository.setBcSmartspaceVisibility(visibility)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 8d02e0e..bb2eeb7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
@@ -165,7 +165,7 @@
             ) { isDozing, biometricUnlockState ->
                 // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
                 // directly from AOD to unlocked while dozing).
-                isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+                isDozing && !BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode)
             }
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
index 2ae5ce1..4c709fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -15,8 +15,19 @@
  */
 package com.android.systemui.keyguard.shared.model
 
+/**
+ * Model [BiometricUnlockMode] with [BiometricUnlockSource].
+ *
+ * @param source can be null as a starting state or if the unlock isn't coming from a biometric (the
+ *   latter should be deprecated in the future, b/338578036)
+ */
+data class BiometricUnlockModel(
+    val mode: BiometricUnlockMode,
+    val source: BiometricUnlockSource?,
+)
+
 /** Model device wakefulness states. */
-enum class BiometricUnlockModel {
+enum class BiometricUnlockMode {
     /** Mode in which we don't need to wake up the device when we authenticate. */
     NONE,
     /**
@@ -60,12 +71,12 @@
                 DISMISS_BOUNCER
             )
 
-        fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
-            return wakeAndUnlockModes.contains(model)
+        fun isWakeAndUnlock(mode: BiometricUnlockMode): Boolean {
+            return wakeAndUnlockModes.contains(mode)
         }
 
-        fun dismissesKeyguard(model: BiometricUnlockModel): Boolean {
-            return dismissesKeyguardModes.contains(model)
+        fun dismissesKeyguard(mode: BiometricUnlockMode): Boolean {
+            return dismissesKeyguardModes.contains(mode)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index cccb93c..b0d45ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -25,6 +25,7 @@
 import androidx.core.view.isInvisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
 import com.android.systemui.common.ui.view.LongPressHandlingView
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -40,6 +41,7 @@
 
 @ExperimentalCoroutinesApi
 object DeviceEntryIconViewBinder {
+    private const val TAG = "DeviceEntryIconViewBinder"
 
     /**
      * Updates UI for:
@@ -82,22 +84,22 @@
             // GONE => AOD transition (even though the view may not be visible until the middle
             // of the transition.
             repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch {
+                launch("$TAG#viewModel.isVisible") {
                     viewModel.isVisible.collect { isVisible ->
                         longPressHandlingView.isInvisible = !isVisible
                     }
                 }
-                launch {
+                launch("$TAG#viewModel.isLongPressEnabled") {
                     viewModel.isLongPressEnabled.collect { isEnabled ->
                         longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
                     }
                 }
-                launch {
+                launch("$TAG#viewModel.accessibilityDelegateHint") {
                     viewModel.accessibilityDelegateHint.collect { hint ->
                         view.accessibilityHintType = hint
                     }
                 }
-                launch {
+                launch("$TAG#viewModel.useBackgroundProtection") {
                     viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
                         if (useBackgroundProtection) {
                             bgView.visibility = View.VISIBLE
@@ -106,7 +108,7 @@
                         }
                     }
                 }
-                launch {
+                launch("$TAG#viewModel.burnInOffsets") {
                     viewModel.burnInOffsets.collect { burnInOffsets ->
                         view.translationX = burnInOffsets.x.toFloat()
                         view.translationY = burnInOffsets.y.toFloat()
@@ -114,7 +116,9 @@
                     }
                 }
 
-                launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } }
+                launch("$TAG#viewModel.deviceEntryViewAlpha") {
+                    viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
+                }
             }
         }
 
@@ -122,7 +126,7 @@
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 // Start with an empty state
                 fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
-                launch {
+                launch("$TAG#fpIconView.viewModel") {
                     fgViewModel.viewModel.collect { viewModel ->
                         fgIconView.setImageState(
                             view.getIconState(viewModel.type, viewModel.useAodVariant),
@@ -144,8 +148,10 @@
 
         bgView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } }
-                launch {
+                launch("$TAG#bgViewModel.alpha") {
+                    bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
+                }
+                launch("$TAG#bgViewModel.color") {
                     bgViewModel.color.collect { color ->
                         bgView.imageTintList = ColorStateList.valueOf(color)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 9ec7a65..487c2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -42,10 +42,10 @@
     val context: Context,
     val keyguardClockViewModel: KeyguardClockViewModel,
     val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
-    val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
+    private val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
     val smartspaceController: LockscreenSmartspaceController,
     val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
-    val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
+    private val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
 ) : KeyguardSection() {
     private var smartspaceView: View? = null
     private var weatherView: View? = null
@@ -61,12 +61,10 @@
         weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout)
         dateView = smartspaceController.buildAndConnectDateView(constraintLayout)
         pastVisibility = smartspaceView?.visibility ?: View.GONE
-        if (keyguardSmartspaceViewModel.isSmartspaceEnabled) {
-            constraintLayout.addView(smartspaceView)
-            if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
-                constraintLayout.addView(weatherView)
-                constraintLayout.addView(dateView)
-            }
+        constraintLayout.addView(smartspaceView)
+        if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
+            constraintLayout.addView(weatherView)
+            constraintLayout.addView(dateView)
         }
         keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
         smartspaceVisibilityListener = OnGlobalLayoutListener {
@@ -205,13 +203,9 @@
 
         constraintSet.apply {
             val weatherVisibility =
-                when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
-                    true -> ConstraintSet.GONE
-                    false ->
-                        when (keyguardSmartspaceViewModel.isWeatherEnabled) {
-                            true -> ConstraintSet.VISIBLE
-                            false -> ConstraintSet.GONE
-                        }
+                when (keyguardSmartspaceViewModel.isWeatherVisible.value) {
+                    true -> ConstraintSet.VISIBLE
+                    false -> ConstraintSet.GONE
                 }
             setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility)
             setAlpha(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 45dca99..e26b75f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -50,6 +50,7 @@
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
 
 /** Models the UI state for the containing device entry icon & long-press handling view. */
 @ExperimentalCoroutinesApi
@@ -110,27 +111,7 @@
             )
         }
 
-    private val dozeAmount: Flow<Float> =
-        combine(
-            transitionInteractor.startedKeyguardTransitionStep,
-            merge(
-                transitionInteractor.transitionStepsFromState(KeyguardState.AOD).map {
-                    1f - it.value
-                },
-                transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.value }
-            ),
-        ) { startedKeyguardTransitionStep, aodTransitionAmount ->
-            if (
-                startedKeyguardTransitionStep.to == KeyguardState.AOD ||
-                    startedKeyguardTransitionStep.from == KeyguardState.AOD
-            ) {
-                aodTransitionAmount
-            } else {
-                // in case a new transition (ie: to occluded) cancels a transition to or from
-                // aod, then we want to make sure the doze amount is still updated to 0
-                0f
-            }
-        }
+    private val dozeAmount: Flow<Float> = transitionInteractor.transitionValue(KeyguardState.AOD)
     // Burn-in offsets that animate based on the transition amount to AOD
     private val animatedBurnInOffsets: Flow<BurnInOffsets> =
         combine(nonAnimatedBurnInOffsets, dozeAmount) { burnInOffsets, dozeAmount ->
@@ -141,54 +122,57 @@
             )
         }
 
-    val deviceEntryViewAlpha: Flow<Float> =
+    val deviceEntryViewAlpha: StateFlow<Float> =
         combine(
-            transitionAlpha,
-            alphaMultiplierFromShadeExpansion,
-        ) { alpha, alphaMultiplier ->
-            alpha * alphaMultiplier
-        }
+                transitionAlpha,
+                alphaMultiplierFromShadeExpansion,
+            ) { alpha, alphaMultiplier ->
+                alpha * alphaMultiplier
+            }
+            .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f)
     val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
     val burnInOffsets: Flow<BurnInOffsets> =
-        deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
-            if (udfpsEnrolled) {
-                combine(
-                    transitionInteractor.startedKeyguardTransitionStep.sample(
-                        shadeInteractor.isAnyFullyExpanded,
-                        ::Pair
-                    ),
-                    animatedBurnInOffsets,
-                    nonAnimatedBurnInOffsets,
-                ) {
-                    (startedTransitionStep, shadeExpanded),
-                    animatedBurnInOffsets,
-                    nonAnimatedBurnInOffsets ->
-                    if (startedTransitionStep.to == KeyguardState.AOD) {
-                        when (startedTransitionStep.from) {
-                            KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
-                            KeyguardState.LOCKSCREEN ->
-                                if (shadeExpanded) {
-                                    nonAnimatedBurnInOffsets
-                                } else {
-                                    animatedBurnInOffsets
-                                }
-                            else -> nonAnimatedBurnInOffsets
+        deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled
+            .flatMapLatest { udfpsEnrolled ->
+                if (udfpsEnrolled) {
+                    combine(
+                        transitionInteractor.startedKeyguardTransitionStep.sample(
+                            shadeInteractor.isAnyFullyExpanded,
+                            ::Pair
+                        ),
+                        animatedBurnInOffsets,
+                        nonAnimatedBurnInOffsets,
+                    ) {
+                        (startedTransitionStep, shadeExpanded),
+                        animatedBurnInOffsets,
+                        nonAnimatedBurnInOffsets ->
+                        if (startedTransitionStep.to == KeyguardState.AOD) {
+                            when (startedTransitionStep.from) {
+                                KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
+                                KeyguardState.LOCKSCREEN ->
+                                    if (shadeExpanded) {
+                                        nonAnimatedBurnInOffsets
+                                    } else {
+                                        animatedBurnInOffsets
+                                    }
+                                else -> nonAnimatedBurnInOffsets
+                            }
+                        } else if (startedTransitionStep.from == KeyguardState.AOD) {
+                            when (startedTransitionStep.to) {
+                                KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
+                                else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
+                            }
+                        } else {
+                            BurnInOffsets(x = 0, y = 0, progress = 0f)
                         }
-                    } else if (startedTransitionStep.from == KeyguardState.AOD) {
-                        when (startedTransitionStep.to) {
-                            KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
-                            else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
-                        }
-                    } else {
-                        BurnInOffsets(x = 0, y = 0, progress = 0f)
                     }
+                } else {
+                    // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need
+                    // to use burn in offsets at all
+                    flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f))
                 }
-            } else {
-                // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need
-                // to use burn in offsets at all
-                flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f))
             }
-        }
+            .distinctUntilChanged()
 
     private val isUnlocked: Flow<Boolean> =
         keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 1f544c1..198e9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -84,8 +84,8 @@
         combine(
                 isLargeClockVisible,
                 currentClock,
-            ) { isLargeClock, clock ->
-                clock?.let { clock ->
+            ) { isLargeClock, currentClock ->
+                currentClock?.let { clock ->
                     val face = if (isLargeClock) clock.largeClock else clock.smallClock
                     face.config.hasCustomWeatherDataDisplay
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index dc053aa..c0b1f95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -41,8 +42,8 @@
     /** Whether the smartspace section is available in the build. */
     val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
     /** Whether the weather area is available in the build. */
-    // TODO(b/317891876): this should be a Flow as the value can change over time.
-    val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
+    private val isWeatherEnabled: StateFlow<Boolean> = smartspaceInteractor.isWeatherEnabled
+
     /** Whether the data and weather areas are decoupled in the build. */
     val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
 
@@ -58,8 +59,10 @@
 
     /** Whether the weather area should be visible. */
     val isWeatherVisible: StateFlow<Boolean> =
-        keyguardClockViewModel.hasCustomWeatherDataDisplay
-            .map { clockIncludesCustomWeatherDisplay ->
+        combine(
+                isWeatherEnabled,
+                keyguardClockViewModel.hasCustomWeatherDataDisplay,
+            ) { isWeatherEnabled, clockIncludesCustomWeatherDisplay ->
                 isWeatherVisible(
                     clockIncludesCustomWeatherDisplay = clockIncludesCustomWeatherDisplay,
                     isWeatherEnabled = isWeatherEnabled,
@@ -72,7 +75,7 @@
                     isWeatherVisible(
                         clockIncludesCustomWeatherDisplay =
                             keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
-                        isWeatherEnabled = isWeatherEnabled,
+                        isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value,
                     )
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index c997617..bf80e18 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -70,8 +70,7 @@
     // dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
     // default behavior. Instead, we want it to run on the view's UI thread since the user will
     // presumably want to call view methods that require being called from said UI thread.
-    val lifecycleCoroutineContext =
-        Dispatchers.Main + createCoroutineTracingContext() + coroutineContext
+    val lifecycleCoroutineContext = MAIN_DISPATCHER_SINGLETON + coroutineContext
     val traceName =
         if (Compile.IS_DEBUG && coroutineTracing()) {
             inferTraceSectionName()
@@ -205,17 +204,28 @@
             StackWalker.getInstance().walk { stream ->
                 stream.filter(::isFrameInteresting).limit(5).findFirst()
             }
-        if (interestingFrame.isPresent) {
+        return if (interestingFrame.isPresent) {
             val f = interestingFrame.get()
-            return "${f.className}#${f.methodName}:${f.lineNumber} [$DEFAULT_TRACE_NAME]"
+            "${f.className}#${f.methodName}:${f.lineNumber} [$DEFAULT_TRACE_NAME]"
         } else {
-            return DEFAULT_TRACE_NAME
+            DEFAULT_TRACE_NAME
         }
     } finally {
         Trace.traceEnd(Trace.TRACE_TAG_APP)
     }
 }
 
+/**
+ * Even though there is only has one usage of `Dispatchers.Main` in this file, we cache it in a
+ * top-level property so that we do not unnecessarily create new `CoroutineContext` objects for
+ * tracing on each call to [repeatWhenAttached]. It is okay to reuse a single instance of the
+ * tracing context because it is copied for its children.
+ *
+ * Also, ideally, we would use the injected `@Main CoroutineDispatcher`, but [repeatWhenAttached] is
+ * an extension function, and plumbing dagger-injected instances for static usage has little
+ * benefit.
+ */
+private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + createCoroutineTracingContext()
 private const val DEFAULT_TRACE_NAME = "repeatWhenAttached"
 private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt"
 private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index ee4eeb8..e3ba36f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -18,20 +18,31 @@
 
 import com.android.systemui.qs.panels.data.repository.IconTilesRepository
 import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
-import com.android.systemui.qs.panels.shared.model.GridLayoutTypeKey
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import com.android.systemui.qs.panels.ui.compose.GridLayout
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
 import dagger.Binds
 import dagger.Module
-import dagger.multibindings.IntoMap
+import dagger.Provides
+import dagger.multibindings.IntoSet
 
 @Module
 interface PanelsModule {
     @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
 
-    @Binds
-    @IntoMap
-    @GridLayoutTypeKey(InfiniteGridLayoutType::class)
-    fun bindGridLayout(impl: InfiniteGridLayout): GridLayout
+    companion object {
+        @Provides
+        @IntoSet
+        fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
+            return Pair(InfiniteGridLayoutType, gridLayout)
+        }
+
+        @Provides
+        fun provideGridLayoutMap(
+            entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
+        ): Map<GridLayoutType, GridLayout> {
+            return entries.toMap()
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
index 920cbe7..68ce5d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
@@ -25,6 +25,5 @@
     fun TileGrid(
         tiles: List<TileViewModel>,
         modifier: Modifier,
-        tile: @Composable (TileViewModel) -> Unit,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index 4d0089e7..e2143e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -16,32 +16,77 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
+import android.graphics.drawable.Animatable
+import android.text.TextUtils
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.integerResource
+import com.android.compose.theme.colorAttr
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.res.R
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.mapLatest
 
-class InfiniteGridLayout @Inject constructor() : GridLayout {
+@SysUISingleton
+class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) :
+    GridLayout {
 
     @Composable
     override fun TileGrid(
         tiles: List<TileViewModel>,
         modifier: Modifier,
-        tile: @Composable (TileViewModel) -> Unit
     ) {
         DisposableEffect(tiles) {
             val token = Any()
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
+        val iconTilesSpecs by
+            iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
 
         LazyVerticalGrid(
             columns =
@@ -54,14 +99,128 @@
                 Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
             modifier = modifier
         ) {
-            tiles.forEach { item(span = { it.span() }) { tile(it) } }
+            items(
+                tiles.size,
+                span = { index ->
+                    val iconOnly = iconTilesSpecs.contains(tiles[index].spec)
+                    if (iconOnly) {
+                        GridItemSpan(1)
+                    } else {
+                        GridItemSpan(2)
+                    }
+                }
+            ) { index ->
+                Tile(
+                    tiles[index],
+                    iconTilesSpecs.contains(tiles[index].spec),
+                    Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+                )
+            }
         }
     }
 
-    private fun TileViewModel.span(): GridItemSpan =
-        if (iconOnly) {
-            GridItemSpan(1)
-        } else {
-            GridItemSpan(2)
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Composable
+    private fun Tile(
+        tile: TileViewModel,
+        iconOnly: Boolean,
+        modifier: Modifier,
+    ) {
+        val state: TileUiState by
+            tile.state
+                .mapLatest { it.toUiState() }
+                .collectAsState(initial = tile.currentState.toUiState())
+        val context = LocalContext.current
+        val horizontalAlignment =
+            if (iconOnly) {
+                Alignment.CenterHorizontally
+            } else {
+                Alignment.Start
+            }
+
+        Row(
+            modifier =
+                modifier
+                    .fillMaxWidth()
+                    .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
+                    .clickable { tile.onClick(null) }
+                    .background(colorAttr(state.colors.background))
+                    .padding(
+                        horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)
+                    ),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement =
+                Arrangement.spacedBy(
+                    space = dimensionResource(id = R.dimen.qs_label_container_margin),
+                    alignment = horizontalAlignment
+                )
+        ) {
+            val icon =
+                remember(state.icon) {
+                    state.icon.get().let {
+                        if (it is QSTileImpl.ResourceIcon) {
+                            Icon.Resource(it.resId, null)
+                        } else {
+                            Icon.Loaded(it.getDrawable(context), null)
+                        }
+                    }
+                }
+            TileIcon(icon, colorAttr(state.colors.icon))
+
+            if (!iconOnly) {
+                Column(
+                    verticalArrangement = Arrangement.Center,
+                    modifier = Modifier.fillMaxHeight()
+                ) {
+                    Text(
+                        state.label.toString(),
+                        color = colorAttr(state.colors.label),
+                        modifier = Modifier.basicMarquee(),
+                    )
+                    if (!TextUtils.isEmpty(state.secondaryLabel)) {
+                        Text(
+                            state.secondaryLabel.toString(),
+                            color = colorAttr(state.colors.secondaryLabel),
+                            modifier = Modifier.basicMarquee(),
+                        )
+                    }
+                }
+            }
         }
+    }
+
+    @OptIn(ExperimentalAnimationGraphicsApi::class)
+    @Composable
+    private fun TileIcon(icon: Icon, color: Color) {
+        val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+        val context = LocalContext.current
+        val loadedDrawable =
+            remember(icon, context) {
+                when (icon) {
+                    is Icon.Loaded -> icon.drawable
+                    is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+                }
+            }
+        if (loadedDrawable !is Animatable) {
+            Icon(
+                icon = icon,
+                tint = color,
+                modifier = modifier,
+            )
+        } else if (icon is Icon.Resource) {
+            val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+            var atEnd by remember(icon.res) { mutableStateOf(false) }
+            LaunchedEffect(key1 = icon.res) {
+                delay(350)
+                atEnd = true
+            }
+            val painter = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+            Image(
+                painter = painter,
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(color = color),
+                modifier = modifier
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
deleted file mode 100644
index 35f2970..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2024 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.systemui.qs.panels.ui.compose
-
-import android.graphics.drawable.Animatable
-import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
-import com.android.compose.theme.colorAttr
-import com.android.systemui.common.shared.model.Icon as IconModel
-import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
-import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.res.R
-import kotlinx.coroutines.delay
-
-@Composable
-fun Tile(
-    tileViewModel: TileViewModel,
-    modifier: Modifier = Modifier,
-) {
-    val state: TileUiState by tileViewModel.state.collectAsState(tileViewModel.currentState)
-    val context = LocalContext.current
-    val horizontalAlignment =
-        if (state.iconOnly) {
-            Alignment.CenterHorizontally
-        } else {
-            Alignment.Start
-        }
-
-    Row(
-        modifier =
-            modifier
-                .fillMaxWidth()
-                .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
-                .clickable { tileViewModel.onClick(null) }
-                .background(colorAttr(state.colors.background))
-                .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)),
-        verticalAlignment = Alignment.CenterVertically,
-        horizontalArrangement =
-            Arrangement.spacedBy(
-                space = dimensionResource(id = R.dimen.qs_label_container_margin),
-                alignment = horizontalAlignment
-            )
-    ) {
-        val icon =
-            remember(state.icon) {
-                state.icon.get().let {
-                    if (it is QSTileImpl.ResourceIcon) {
-                        IconModel.Resource(it.resId, null)
-                    } else {
-                        IconModel.Loaded(it.getDrawable(context), null)
-                    }
-                }
-            }
-        TileIcon(icon, colorAttr(state.colors.icon))
-
-        if (!state.iconOnly) {
-            Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
-                Text(
-                    state.label.toString(),
-                    color = colorAttr(state.colors.label),
-                    modifier = Modifier.basicMarquee(),
-                )
-                if (!TextUtils.isEmpty(state.secondaryLabel)) {
-                    Text(
-                        state.secondaryLabel.toString(),
-                        color = colorAttr(state.colors.secondaryLabel),
-                        modifier = Modifier.basicMarquee(),
-                    )
-                }
-            }
-        }
-    }
-}
-
-@OptIn(ExperimentalAnimationGraphicsApi::class)
-@Composable
-private fun TileIcon(icon: IconModel, color: Color) {
-    val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
-    val context = LocalContext.current
-    val loadedDrawable =
-        remember(icon, context) {
-            when (icon) {
-                is IconModel.Loaded -> icon.drawable
-                is IconModel.Resource -> AppCompatResources.getDrawable(context, icon.res)
-            }
-        }
-    if (loadedDrawable !is Animatable) {
-        Icon(
-            icon = icon,
-            tint = color,
-            modifier = modifier,
-        )
-    } else if (icon is IconModel.Resource) {
-        val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
-        var atEnd by remember(icon.res) { mutableStateOf(false) }
-        LaunchedEffect(key1 = icon.res) {
-            delay(350)
-            atEnd = true
-        }
-        val painter = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
-        Image(
-            painter = painter,
-            contentDescription = null,
-            colorFilter = ColorFilter.tint(color = color),
-            modifier = modifier
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
index a528eed..2f32d72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
@@ -16,21 +16,16 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
-import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.dimensionResource
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
-import com.android.systemui.res.R
 
 @Composable
 fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) {
-    val gridLayout by viewModel.gridLayout.collectAsState(InfiniteGridLayout())
+    val gridLayout by viewModel.gridLayout.collectAsState()
     val tiles by viewModel.tileViewModels.collectAsState(emptyList())
 
-    gridLayout.TileGrid(tiles, modifier) {
-        Tile(it, modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)))
-    }
+    gridLayout.TileGrid(tiles, modifier)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt
index fc13460..5eee691 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt
@@ -17,34 +17,39 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import com.android.systemui.qs.panels.ui.compose.GridLayout
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class TileGridViewModel
 @Inject
 constructor(
     gridLayoutTypeInteractor: GridLayoutTypeInteractor,
-    gridLayoutMap: Map<Class<out GridLayoutType>, @JvmSuppressWildcards GridLayout>,
+    gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
     tilesInteractor: CurrentTilesInteractor,
-    iconTilesInteractor: IconTilesInteractor,
+    defaultGridLayout: InfiniteGridLayout,
+    @Application private val applicationScope: CoroutineScope
 ) {
-    val gridLayout: Flow<GridLayout> =
-        gridLayoutTypeInteractor.layout.map {
-            gridLayoutMap[it::class.java] ?: InfiniteGridLayout()
-        }
+    val gridLayout: StateFlow<GridLayout> =
+        gridLayoutTypeInteractor.layout
+            .map { gridLayoutMap[it] ?: defaultGridLayout }
+            .stateIn(applicationScope, SharingStarted.Eagerly, defaultGridLayout)
     val tileViewModels: Flow<List<TileViewModel>> =
-        combine(tilesInteractor.currentTiles, iconTilesInteractor.iconTilesSpecs) {
-            tiles,
-            iconTilesSpecs ->
-            tiles.map { TileViewModel(it.tile, iconTilesSpecs.contains(it.spec)) }
+        tilesInteractor.currentTiles.mapLatest { tiles ->
+            tiles.map { TileViewModel(it.tile, it.spec) }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index f4b7255..58d07c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -24,15 +24,13 @@
     val secondaryLabel: CharSequence,
     val colors: TileColorAttributes,
     val icon: Supplier<QSTile.Icon>,
-    val iconOnly: Boolean,
 )
 
-fun QSTile.State.toUiState(iconOnly: Boolean): TileUiState {
+fun QSTile.State.toUiState(): TileUiState {
     return TileUiState(
         label ?: "",
         secondaryLabel ?: "",
         colors(),
         icon?.let { Supplier { icon } } ?: iconSupplier,
-        iconOnly,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index 08e9119..8fc66d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -19,16 +19,16 @@
 import android.view.View
 import android.view.View.OnLongClickListener
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
-class TileViewModel(private val tile: QSTile, val iconOnly: Boolean = false) :
+class TileViewModel(private val tile: QSTile, val spec: TileSpec) :
     OnLongClickListener, View.OnClickListener {
-    val state: Flow<TileUiState> =
+    val state: Flow<QSTile.State> =
         conflatedCallbackFlow {
                 val callback = QSTile.Callback { trySend(it.copy()) }
 
@@ -37,11 +37,10 @@
                 awaitClose { tile.removeCallback(callback) }
             }
             .onStart { emit(tile.state) }
-            .map { it.toUiState(iconOnly) }
             .distinctUntilChanged()
 
-    val currentState: TileUiState
-        get() = tile.state.toUiState(iconOnly)
+    val currentState: QSTile.State
+        get() = tile.state
 
     override fun onClick(view: View?) {
         tile.click(view)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index 93021ba..cfcea98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -91,7 +91,15 @@
             .queryIntentServicesAsUser(INTENT, FLAGS, userId)
             .mapNotNull { it.serviceInfo }
             .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
-            .filter { packageManager.isComponentActuallyEnabled(it) }
+            .filter {
+                try {
+                    packageManager.isComponentActuallyEnabled(it)
+                } catch (e: IllegalArgumentException) {
+                    // If the package is not found, it means it was uninstalled between query
+                    // and now. So it's clearly not enabled.
+                    false
+                }
+            }
             .mapTo(mutableSetOf()) { it.componentName }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
index a2ded6a..71cf9e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
@@ -22,8 +22,8 @@
  * These properties are used during animation if a tile supports a long-press action.
  */
 data class QSLongPressProperties(
-    var xScale: Float,
-    var yScale: Float,
+    var height: Float,
+    var width: Float,
     var cornerRadius: Float,
     var backgroundColor: Int,
     var labelColor: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 40cf4a4..ca5b771 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -25,6 +25,7 @@
 import android.content.res.Resources.ID_NULL
 import android.graphics.Color
 import android.graphics.PorterDuff
+import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.LayerDrawable
@@ -46,6 +47,7 @@
 import android.widget.Switch
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.drawable.updateBounds
 import com.android.app.tracing.traceSection
 import com.android.settingslib.Utils
 import com.android.systemui.Flags
@@ -62,7 +64,6 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
 import com.android.systemui.res.R
-import com.android.systemui.util.children
 import kotlinx.coroutines.DisposableHandle
 import java.util.Objects
 
@@ -83,6 +84,10 @@
         const val UNAVAILABLE_ALPHA = 0.3f
         @VisibleForTesting
         internal const val TILE_STATE_RES_PREFIX = "tile_states_"
+        @VisibleForTesting
+        internal const val LONG_PRESS_EFFECT_WIDTH_SCALE = 1.1f
+        @VisibleForTesting
+        internal const val LONG_PRESS_EFFECT_HEIGHT_SCALE = 1.2f
     }
 
     private val icon: QSIconViewImpl = QSIconViewImpl(context)
@@ -180,6 +185,8 @@
     private val locInScreen = IntArray(2)
 
     /** Visuo-haptic long-press effects */
+    private var haveLongPressPropertiesBeenReset = true
+    private var paddingForLaunch = Rect()
     private var initialLongPressProperties: QSLongPressProperties? = null
     private var finalLongPressProperties: QSLongPressProperties? = null
     private val colorEvaluator = ArgbEvaluator.getInstance()
@@ -326,7 +333,7 @@
     private fun updateHeight() {
         // TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the
         //  launch animation.
-        if (scaleX != 1f || scaleY != 1f) {
+        if (!haveLongPressPropertiesBeenReset && longPressEffect != null) {
             // The launch animation of a long-press effect did not reset the long-press effect so
             // we must do it here
             resetLongPressEffectProperties()
@@ -632,7 +639,7 @@
                     )
             }
             showRippleEffect = false
-            initializeLongPressProperties()
+            initializeLongPressProperties(measuredHeight, measuredWidth)
         } else {
             // Long-press effects might have been enabled before but the new state does not
             // handle a long-press. In this case, we go back to the behaviour of a regular tile
@@ -765,8 +772,60 @@
 
     override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
 
+    fun prepareForLaunch() {
+        val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
+        val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
+        val deltaH = finalLongPressProperties?.height?.minus(startingHeight)?.toInt() ?: 0
+        val deltaW = finalLongPressProperties?.width?.minus(startingWidth)?.toInt() ?: 0
+        paddingForLaunch.left = -deltaW / 2
+        paddingForLaunch.top = -deltaH / 2
+        paddingForLaunch.right = deltaW / 2
+        paddingForLaunch.bottom = deltaH / 2
+    }
+
+    override fun getPaddingForLaunchAnimation(): Rect = paddingForLaunch
+
     fun updateLongPressEffectProperties(effectProgress: Float) {
         if (!isLongClickable || longPressEffect == null) return
+
+        if (haveLongPressPropertiesBeenReset) haveLongPressPropertiesBeenReset = false
+
+        // Dimensions change
+        val newHeight =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.height ?: 0f,
+                finalLongPressProperties?.height ?: 0f,
+            ).toInt()
+        val newWidth =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.width ?: 0f,
+                finalLongPressProperties?.width ?: 0f,
+            ).toInt()
+
+        val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
+        val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
+        val deltaH = (newHeight - startingHeight) / 2
+        val deltaW = (newWidth - startingWidth) / 2
+
+        background.updateBounds(
+            left = -deltaW,
+            top = -deltaH,
+            right = newWidth - deltaW,
+            bottom = newHeight - deltaH,
+        )
+
+        // Radius change
+        val newRadius =
+            interpolateFloat(
+                effectProgress,
+                initialLongPressProperties?.cornerRadius ?: 0f,
+                finalLongPressProperties?.cornerRadius ?: 0f,
+            )
+        changeCornerRadius(newRadius)
+
+        // Color change
         setAllColors(
             colorEvaluator.evaluate(
                 effectProgress,
@@ -802,32 +861,6 @@
                 finalLongPressProperties?.iconColor ?: 0,
             ) as Int,
         )
-
-        val newScaleX =
-            interpolateFloat(
-                effectProgress,
-                initialLongPressProperties?.xScale ?: 1f,
-                finalLongPressProperties?.xScale ?: 1f,
-            )
-        val newScaleY =
-            interpolateFloat(
-                effectProgress,
-                initialLongPressProperties?.xScale ?: 1f,
-                finalLongPressProperties?.xScale ?: 1f,
-            )
-        val newRadius =
-            interpolateFloat(
-                effectProgress,
-                initialLongPressProperties?.cornerRadius ?: 0f,
-                finalLongPressProperties?.cornerRadius ?: 0f,
-            )
-        scaleX = newScaleX
-        scaleY = newScaleY
-        for (child in children) {
-            child.scaleX = 1f / newScaleX
-            child.scaleY = 1f / newScaleY
-        }
-        changeCornerRadius(newRadius)
     }
 
     private fun unbindLongPressEffect() {
@@ -839,12 +872,12 @@
         start + fraction * (end - start)
 
     fun resetLongPressEffectProperties() {
-        scaleY = 1f
-        scaleX = 1f
-        for (child in children) {
-            child.scaleY = 1f
-            child.scaleX = 1f
-        }
+        background.updateBounds(
+            left = 0,
+            top = 0,
+            right = initialLongPressProperties?.width?.toInt() ?: 0,
+            bottom = initialLongPressProperties?.height?.toInt() ?: 0,
+        )
         changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
         setAllColors(
             getBackgroundColorForState(lastState, lastDisabledByPolicy),
@@ -854,13 +887,15 @@
             getOverlayColorForState(lastState),
         )
         icon.setTint(icon.mIcon as ImageView, lastIconTint)
+        haveLongPressPropertiesBeenReset = true
     }
 
-    private fun initializeLongPressProperties() {
+    @VisibleForTesting
+    fun initializeLongPressProperties(startingHeight: Int, startingWidth: Int) {
         initialLongPressProperties =
             QSLongPressProperties(
-                /* xScale= */1f,
-                /* yScale= */1f,
+                height = startingHeight.toFloat(),
+                width = startingWidth.toFloat(),
                 resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
                 getBackgroundColorForState(lastState),
                 getLabelColorForState(lastState),
@@ -872,8 +907,8 @@
 
         finalLongPressProperties =
             QSLongPressProperties(
-                /* xScale= */1.1f,
-                /* yScale= */1.2f,
+                height = LONG_PRESS_EFFECT_HEIGHT_SCALE * startingHeight,
+                width = LONG_PRESS_EFFECT_WIDTH_SCALE * startingWidth,
                 resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
                 getBackgroundColorForState(Tile.STATE_ACTIVE),
                 getLabelColorForState(Tile.STATE_ACTIVE),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index ff5fdc6..851bfca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -229,7 +229,9 @@
         // the gesture area doesn't overlap with widgets.
         // TODO(b/323035776): adjust gesture areaa for portrait mode
         containerView.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
+            // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
+            // occluded.
+            lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                 val exclusionRect =
                     Rect(
                         0,
@@ -242,12 +244,19 @@
             }
         }
 
+        // Listen to bouncer visibility directly as these flows become true as soon as any portion
+        // of the bouncers are visible when the transition starts. The keyguard transition state
+        // only changes once transitions are fully finished, which would mean touches during a
+        // transition to the bouncer would be incorrectly intercepted by the hub.
         collectFlow(
             containerView,
-            keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
+            or(
+                keyguardInteractor.primaryBouncerShowing,
+                keyguardInteractor.alternateBouncerShowing
+            ),
             {
                 anyBouncerShowing = it
-                updateLifecycleState()
+                updateTouchHandlingState()
             }
         )
         collectFlow(
@@ -255,7 +264,7 @@
             communalInteractor.isCommunalShowing,
             {
                 hubShowing = it
-                updateLifecycleState()
+                updateTouchHandlingState()
             }
         )
         collectFlow(
@@ -263,7 +272,7 @@
             and(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
             {
                 shadeShowing = it
-                updateLifecycleState()
+                updateTouchHandlingState()
             }
         )
         collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
@@ -276,13 +285,22 @@
     /**
      * Updates the lifecycle stored by the [lifecycleRegistry] to control when the [touchMonitor]
      * should listen for and intercept top and bottom swipes.
+     *
+     * Also clears gesture exclusion zones when the hub is occluded or gone.
      */
-    private fun updateLifecycleState() {
+    private fun updateTouchHandlingState() {
         val shouldInterceptGestures = hubShowing && !(shadeShowing || anyBouncerShowing)
         if (shouldInterceptGestures) {
             lifecycleRegistry.currentState = Lifecycle.State.RESUMED
         } else {
+            // Hub is either occluded or no longer showing, turn off touch handling.
             lifecycleRegistry.currentState = Lifecycle.State.STARTED
+
+            // Clear exclusion rects if the hub is not showing or is covered, so we don't interfere
+            // with back gestures when the bouncer or shade. We do this here instead of with
+            // repeatOnLifecycle as repeatOnLifecycle does not run when going from RESUMED back to
+            // STARTED, only when going from CREATED to STARTED.
+            communalContainerView!!.systemGestureExclusionRects = emptyList()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 4d4d177..9f1b423 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -25,6 +25,7 @@
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.Flags.predictiveBackAnimateShade;
+import static com.android.systemui.Flags.shadeCollapseActivityLaunchFix;
 import static com.android.systemui.Flags.smartspaceRelocateToBottom;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.GENERIC;
@@ -4102,7 +4103,11 @@
 
     @Override
     public boolean canBeCollapsed() {
-        return !isFullyCollapsed() && !isTracking() && !isClosing();
+        return !isFullyCollapsed() && !isTracking() && !isClosing()
+                // Don't try to collapse if on keyguard, as the expansion fraction is 1 in this
+                // case.
+                && !(shadeCollapseActivityLaunchFix() && mExpandedFraction == 1f
+                && mBarState == KEYGUARD);
     }
 
     public void instantCollapse() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
new file mode 100644
index 0000000..b8dd628
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.systemui.shade.ui.viewmodel
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Models UI state and handles user input for the overlay shade UI, which shows a shade as an
+ * overlay on top of another scene UI.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class OverlayShadeViewModel
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val sceneInteractor: SceneInteractor,
+    deviceEntryInteractor: DeviceEntryInteractor,
+) {
+    /** The scene to show in the background when the overlay shade is open. */
+    val backgroundScene: StateFlow<SceneKey> =
+        deviceEntryInteractor.isDeviceEntered
+            .map(::backgroundScene)
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = backgroundScene(deviceEntryInteractor.isDeviceEntered.value)
+            )
+
+    /** Notifies that the user has clicked the semi-transparent background scrim. */
+    fun onScrimClicked() {
+        sceneInteractor.changeScene(
+            toScene = backgroundScene.value,
+            loggingReason = "Shade scrim clicked",
+        )
+    }
+
+    private fun backgroundScene(isDeviceEntered: Boolean): SceneKey {
+        return if (isDeviceEntered) Scenes.Gone else Scenes.Lockscreen
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 4d7dacd..7c1101b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -382,16 +382,16 @@
 
     private void clearCurrentMediaNotificationSession() {
         mMediaMetadata = null;
-        if (mMediaController != null) {
-            if (DEBUG_MEDIA) {
-                Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
-                        + mMediaController.getPackageName());
-            }
-            mBackgroundExecutor.execute(() -> {
+        mBackgroundExecutor.execute(() -> {
+            if (mMediaController != null) {
+                if (DEBUG_MEDIA) {
+                    Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+                            + mMediaController.getPackageName());
+                }
                 mMediaController.unregisterCallback(mMediaListener);
                 mMediaController = null;
-            });
-        }
+            }
+        });
     }
 
     public interface MediaListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 2f9c2f0..6909907 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -82,7 +82,7 @@
         launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
         launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
         launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
-        launch { viewModel.expandFraction.collect { view.setExpandFraction(it) } }
+        launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } }
         launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
         launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
 
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 79e6a0a..f83aed8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.log.SessionTracker;
@@ -334,7 +335,7 @@
     @VisibleForTesting
     protected void consumeTransitionStepOnStartedKeyguardState(TransitionStep transitionStep) {
         if (transitionStep.getFrom() == KeyguardState.GONE) {
-            mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE);
+            mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE, null);
         }
     }
 
@@ -409,7 +410,10 @@
             Trace.endSection();
             return;
         }
-        startWakeAndUnlock(MODE_SHOW_BOUNCER);
+        startWakeAndUnlock(
+                MODE_SHOW_BOUNCER,
+                BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+        );
     }
 
     @Override
@@ -456,10 +460,19 @@
                 || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
             onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
         }
-        startWakeAndUnlock(mode);
+        startWakeAndUnlock(
+                mode,
+                BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+        );
     }
 
-    public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {
+    /**
+     * Wake and unlock the device in response to successful authentication using biometrics.
+     */
+    public void startWakeAndUnlock(
+            @WakeAndUnlockMode int mode,
+            BiometricUnlockSource biometricUnlockSource
+    ) {
         mLogger.logStartWakeAndUnlock(mode);
         boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
         mMode = mode;
@@ -532,15 +545,18 @@
             case MODE_NONE:
                 break;
         }
-        onModeChanged(mMode);
+        onModeChanged(mMode, biometricUnlockSource);
         Trace.endSection();
     }
 
-    private void onModeChanged(@WakeAndUnlockMode int mode) {
+    private void onModeChanged(
+            @WakeAndUnlockMode int mode,
+            BiometricUnlockSource biometricUnlockSource
+    ) {
         for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
             listener.onModeChanged(mode);
         }
-        mBiometricUnlockInteractor.setBiometricUnlockState(mode);
+        mBiometricUnlockInteractor.setBiometricUnlockState(mode, biometricUnlockSource);
     }
 
     private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) {
@@ -719,7 +735,10 @@
         final boolean screenOff = !mUpdateMonitor.isDeviceInteractive();
         if (!mVibratorHelper.hasVibrator() && screenOff) {
             mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)");
-            startWakeAndUnlock(MODE_ONLY_WAKE);
+            startWakeAndUnlock(
+                    MODE_ONLY_WAKE,
+                    BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+            );
         } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
                 && mUpdateMonitor.isUdfpsSupported()) {
             long currUptimeMillis = mSystemClock.uptimeMillis();
@@ -732,7 +751,10 @@
 
             if (mNumConsecutiveFpFailures >= UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
                 mLogger.logUdfpsAttemptThresholdMet(mNumConsecutiveFpFailures);
-                startWakeAndUnlock(MODE_SHOW_BOUNCER);
+                startWakeAndUnlock(
+                        MODE_SHOW_BOUNCER,
+                        BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+                );
                 UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
                 mNumConsecutiveFpFailures = 0;
             }
@@ -755,7 +777,10 @@
                 || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
         if (fingerprintLockout) {
             mLogger.d("fingerprint locked out");
-            startWakeAndUnlock(MODE_SHOW_BOUNCER);
+            startWakeAndUnlock(
+                    MODE_SHOW_BOUNCER,
+                    BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+            );
             UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
         }
 
@@ -897,7 +922,8 @@
     public interface BiometricUnlockEventsListener {
         /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
         default void onResetMode() {}
-        /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
+        /** Called when {@code mMode} has changed in
+         *      {@link #startWakeAndUnlock(int, BiometricUnlockSource)}. */
         default void onModeChanged(@WakeAndUnlockMode int mode) {}
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 6b68511..bcc7db1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -417,10 +417,10 @@
         ) {
             // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a
             // pulse.
-            // TODO: Factor this transition out of BiometricUnlockController.
+            // TODO (b/338578036): Factor this transition out of BiometricUnlockController.
             biometricUnlockControllerLazy
                 .get()
-                .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+                .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, null)
         }
         if (keyguardStateController.isShowing) {
             statusBarKeyguardViewManagerLazy
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 054116d..51c053e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -84,14 +84,15 @@
         if (Flags.oemEnabledSatelliteFlag()) {
                 iconsInteractor.icons.aggregateOver(
                     selector = { intr ->
-                        combine(intr.isInService, intr.isEmergencyOnly) {
+                        combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
                             isInService,
-                            isEmergencyOnly ->
-                            !isInService && !isEmergencyOnly
+                            isEmergencyOnly,
+                            isNtn ->
+                            !isInService && !(isEmergencyOnly || isNtn)
                         }
                     }
-                ) { isOosAndIsNotEmergencyOnly ->
-                    isOosAndIsNotEmergencyOnly.all { it }
+                ) { isOosAndNotEmergencyOnlyOrSatellite ->
+                    isOosAndNotEmergencyOnlyOrSatellite.all { it }
                 }
             } else {
                 flowOf(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 43c860c..2b7e7ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
@@ -162,7 +162,7 @@
         kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
             BiometricUnlockSource.FINGERPRINT_SENSOR
         )
-        kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
     }
 
     private fun fingerprintFailure() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
new file mode 100644
index 0000000..a320845
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyguard.data.repository
+
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import kotlin.test.Test
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardSmartspaceRepositoryImplTest : SysuiTestCase() {
+
+    private lateinit var scheduler: TestCoroutineScheduler
+    private lateinit var dispatcher: CoroutineDispatcher
+    private lateinit var scope: TestScope
+
+    private lateinit var underTest: KeyguardSmartspaceRepository
+    private lateinit var fakeSettings: FakeSettings
+    private lateinit var fakeUserTracker: FakeUserTracker
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        fakeSettings = FakeSettings()
+        fakeUserTracker = FakeUserTracker()
+        fakeSettings.userId = fakeUserTracker.userId
+        scheduler = TestCoroutineScheduler()
+        dispatcher = StandardTestDispatcher(scheduler)
+        scope = TestScope(dispatcher)
+        underTest =
+            KeyguardSmartspaceRepositoryImpl(
+                context = context,
+                secureSettings = fakeSettings,
+                userTracker = fakeUserTracker,
+                applicationScope = scope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun testWeatherEnabled_true() =
+        scope.runTest {
+            fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, 1)
+            val value = collectLastValue(underTest.isWeatherEnabled)
+            Truth.assertThat(value()).isEqualTo(true)
+        }
+
+    @Test
+    fun testWeatherEnabled_false() =
+        scope.runTest {
+            fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, 0)
+
+            val value = collectLastValue(underTest.isWeatherEnabled)
+            Truth.assertThat(value()).isEqualTo(false)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index cded2a4..593cfde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -45,8 +45,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
@@ -99,7 +98,7 @@
                 to = KeyguardState.DOZING,
                 testScope
             )
-            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+            kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
             reset(transitionRepository)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 8435a03..7c92ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -42,8 +42,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testScope
@@ -87,7 +86,7 @@
                 to = KeyguardState.DREAMING,
                 testScope
             )
-            kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+            kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
             reset(transitionRepository)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3827046..e94efd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -34,7 +34,7 @@
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -358,7 +358,7 @@
 
             // WHEN biometrics succeeds with wake and unlock from dream mode
             keyguardRepository.setBiometricUnlockState(
-                BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+                BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM
             )
             runCurrent()
 
@@ -535,7 +535,7 @@
 
             // WHEN biometrics succeeds with wake and unlock mode
             powerInteractor.setAwakeForTest()
-            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
             advanceTimeBy(60L)
 
             assertThat(transitionRepository)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 8eccde7..be10b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -64,6 +64,7 @@
 
     private val clockShouldBeCentered = MutableStateFlow(false)
     private val hasCustomWeatherDataDisplay = MutableStateFlow(false)
+    private val isWeatherVisibleFlow = MutableStateFlow(false)
 
     @Before
     fun setup() {
@@ -89,7 +90,7 @@
             .thenReturn(hasCustomWeatherDataDisplay)
         whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
         whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
-
+        whenever(keyguardSmartspaceViewModel.isWeatherVisible).thenReturn(isWeatherVisibleFlow)
         constraintSet = ConstraintSet()
     }
 
@@ -124,7 +125,6 @@
     @Test
     fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
         whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
-        hasCustomWeatherDataDisplay.value = false
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
         assertWeatherSmartspaceConstrains(constraintSet)
@@ -149,13 +149,12 @@
 
     @Test
     fun testNormalDateWeatherVisibility() {
-        hasCustomWeatherDataDisplay.value = false
-        whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(true)
+        isWeatherVisibleFlow.value = true
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
         assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(VISIBLE)
 
-        whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(false)
+        isWeatherVisibleFlow.value = false
         underTest.applyConstraints(constraintSet)
         assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(GONE)
         assertThat(constraintSet.getVisibility(dateView.id)).isEqualTo(VISIBLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
new file mode 100644
index 0000000..78f4dcc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardSmartspaceRepository
+import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardSmartspaceViewModelTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val underTest = kosmos.keyguardSmartspaceViewModel
+    val res = context.resources
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        kosmos.fakeKeyguardClockRepository.setCurrentClock(clockController)
+    }
+
+    @Test
+    fun testWhenWeatherEnabled_notCustomWeatherDataDisplay_isWeatherVisible_shouldBeTrue() =
+        testScope.runTest {
+            val isWeatherVisible by collectLastValue(underTest.isWeatherVisible)
+            whenever(clockController.largeClock.config.hasCustomWeatherDataDisplay)
+                .thenReturn(false)
+
+            with(kosmos) {
+                keyguardSmartspaceRepository.setIsWeatherEnabled(true)
+                keyguardClockRepository.setClockSize(ClockSize.LARGE)
+            }
+
+            assertThat(isWeatherVisible).isEqualTo(true)
+        }
+
+    @Test
+    fun testWhenWeatherEnabled_hasCustomWeatherDataDisplay_isWeatherVisible_shouldBeFalse() =
+        testScope.runTest {
+            val isWeatherVisible by collectLastValue(underTest.isWeatherVisible)
+            whenever(clockController.largeClock.config.hasCustomWeatherDataDisplay).thenReturn(true)
+
+            with(kosmos) {
+                keyguardSmartspaceRepository.setIsWeatherEnabled(true)
+                keyguardClockRepository.setClockSize(ClockSize.LARGE)
+            }
+
+            assertThat(isWeatherVisible).isEqualTo(false)
+        }
+
+    @Test
+    fun testWhenWeatherEnabled_notCustomWeatherDataDisplay_notIsWeatherVisible_shouldBeFalse() =
+        testScope.runTest {
+            val isWeatherVisible by collectLastValue(underTest.isWeatherVisible)
+            whenever(clockController.largeClock.config.hasCustomWeatherDataDisplay)
+                .thenReturn(false)
+
+            with(kosmos) {
+                keyguardSmartspaceRepository.setIsWeatherEnabled(false)
+                keyguardClockRepository.setClockSize(ClockSize.LARGE)
+            }
+
+            assertThat(isWeatherVisible).isEqualTo(false)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt
deleted file mode 100644
index e8c5fd9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2024 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.systemui.qs.panels.ui.viewmodel
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.qs.FakeQSFactory
-import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
-import com.android.systemui.qs.pipeline.domain.interactor.FakeQSTile
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.qsTileFactory
-import com.android.systemui.testKosmos
-import kotlinx.coroutines.test.runTest
-import org.junit.Assert
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class TileGridViewModelTest : SysuiTestCase() {
-
-    private val kosmos = testKosmos().apply { qsTileFactory = FakeQSFactory(::tileCreator) }
-    private val underTest = with(kosmos) { tileGridViewModel }
-
-    @Test
-    fun noIconTiles() =
-        with(kosmos) {
-            testScope.runTest {
-                val latest by collectLastValue(underTest.tileViewModels)
-
-                tileSpecRepository.setTiles(
-                    0,
-                    listOf(
-                        TileSpec.create("bluetooth"),
-                        TileSpec.create("internet"),
-                        TileSpec.create("alarm")
-                    )
-                )
-
-                latest!!.forEach { Assert.assertFalse(it.iconOnly) }
-            }
-        }
-
-    @Test
-    fun withIconTiles() =
-        with(kosmos) {
-            testScope.runTest {
-                val latest by collectLastValue(underTest.tileViewModels)
-
-                tileSpecRepository.setTiles(
-                    0,
-                    listOf(
-                        TileSpec.create("airplane"),
-                        TileSpec.create("flashlight"),
-                        TileSpec.create("rotation")
-                    )
-                )
-
-                latest!!.forEach { Assert.assertTrue(it.iconOnly) }
-            }
-        }
-
-    private fun tileCreator(spec: String): QSTile {
-        return FakeQSTile(0).apply { tileSpec = spec }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index ecbd0f5..b5ef8c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tileimpl
 
 import android.content.Context
+import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.service.quicksettings.Tile
 import android.testing.AndroidTestingRunner
@@ -474,6 +475,31 @@
         assertThat(tileView.isLongPressEffectInitialized).isFalse()
     }
 
+    @Test
+    fun onPrepareForLaunch_paddingForLaunchAnimationIsConfigured() {
+        val startingWidth = 100
+        val startingHeight = 50
+        val deltaWidth = (QSTileViewImpl.LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * startingWidth
+        val deltaHeight = (QSTileViewImpl.LONG_PRESS_EFFECT_HEIGHT_SCALE - 1f) * startingHeight
+
+        // GIVEN that long-press effect properties are initialized
+        tileView.initializeLongPressProperties(startingHeight, startingWidth)
+
+        // WHEN the tile is preparing for the launch animation
+        tileView.prepareForLaunch()
+
+        // THE animation padding corresponds to the tile's growth due to the effect
+        val padding = tileView.getPaddingForLaunchAnimation()
+        assertThat(padding).isEqualTo(
+            Rect(
+                -deltaWidth.toInt() / 2,
+                -deltaHeight.toInt() / 2,
+                deltaWidth.toInt() / 2,
+                deltaHeight.toInt() / 2,
+            )
+        )
+    }
+
     class FakeTileView(
         context: Context,
         collapsed: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 19b137c..99204e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.ambient.touch.TouchHandler
 import com.android.systemui.ambient.touch.TouchMonitor
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.fakeCommunalRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -42,10 +43,8 @@
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -238,11 +237,7 @@
                 goToScene(CommunalScenes.Communal)
 
                 // Bouncer is visible.
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    KeyguardState.GLANCEABLE_HUB,
-                    KeyguardState.PRIMARY_BOUNCER,
-                    testScope
-                )
+                fakeKeyguardBouncerRepository.setPrimaryShow(true)
                 testableLooper.processAllMessages()
 
                 // Touch events are not intercepted.
@@ -368,11 +363,7 @@
                 goToScene(CommunalScenes.Communal)
 
                 // Bouncer is visible.
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    KeyguardState.GLANCEABLE_HUB,
-                    KeyguardState.PRIMARY_BOUNCER,
-                    testScope
-                )
+                fakeKeyguardBouncerRepository.setPrimaryShow(true)
                 testableLooper.processAllMessages()
 
                 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
@@ -387,11 +378,7 @@
                 goToScene(CommunalScenes.Communal)
 
                 // Bouncer is visible.
-                fakeKeyguardTransitionRepository.sendTransitionSteps(
-                    KeyguardState.GLANCEABLE_HUB,
-                    KeyguardState.ALTERNATE_BOUNCER,
-                    testScope
-                )
+                fakeKeyguardBouncerRepository.setAlternateVisible(true)
                 testableLooper.processAllMessages()
 
                 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
@@ -452,6 +439,53 @@
             }
         }
 
+    @Test
+    fun gestureExclusionZone_unsetWhenShadeOpen() =
+        with(kosmos) {
+            testScope.runTest {
+                goToScene(CommunalScenes.Communal)
+
+                // Shade shows up.
+                shadeTestUtil.setQsExpansion(1.0f)
+                testableLooper.processAllMessages()
+
+                // Exclusion rects are unset.
+                assertThat(containerView.systemGestureExclusionRects).isEmpty()
+            }
+        }
+
+    @Test
+    fun gestureExclusionZone_unsetWhenBouncerOpen() =
+        with(kosmos) {
+            testScope.runTest {
+                goToScene(CommunalScenes.Communal)
+
+                // Bouncer is visible.
+                fakeKeyguardBouncerRepository.setPrimaryShow(true)
+                testableLooper.processAllMessages()
+
+                // Exclusion rects are unset.
+                assertThat(containerView.systemGestureExclusionRects).isEmpty()
+            }
+        }
+
+    @Test
+    fun gestureExclusionZone_unsetWhenHubClosed() =
+        with(kosmos) {
+            testScope.runTest {
+                goToScene(CommunalScenes.Communal)
+
+                // Exclusion rect is set.
+                assertThat(containerView.systemGestureExclusionRects).hasSize(1)
+
+                // Leave the hub.
+                goToScene(CommunalScenes.Blank)
+
+                // Exclusion rect is unset.
+                assertThat(containerView.systemGestureExclusionRects).isEmpty()
+            }
+        }
+
     private fun initAndAttachContainerView() {
         containerView = View(context)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 81e20c1..6b57f6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -47,6 +48,7 @@
 import android.graphics.Point;
 import android.os.PowerManager;
 import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -675,6 +677,32 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX)
+    public void testCanBeCollapsed_expandedInKeyguard() {
+        mStatusBarStateController.setState(KEYGUARD);
+        mNotificationPanelViewController.setExpandedFraction(1f);
+
+        assertThat(mNotificationPanelViewController.canBeCollapsed()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX)
+    public void testCanBeCollapsed_expandedInShade() {
+        mStatusBarStateController.setState(SHADE);
+        mNotificationPanelViewController.setExpandedFraction(1f);
+        assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue();
+    }
+
+    @Test
+    @DisableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX)
+    public void testCanBeCollapsed_expandedInKeyguard_flagDisabled() {
+        mStatusBarStateController.setState(KEYGUARD);
+        mNotificationPanelViewController.setExpandedFraction(1f);
+
+        assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue();
+    }
+
+    @Test
     public void testSwipeWhileLocked_notifiesKeyguardState() {
         mStatusBarStateController.setState(KEYGUARD);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 317e35c..04fa590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -216,6 +216,7 @@
                 keyguardTransitionInteractor,
                 () -> sceneInteractor,
                 () -> mKosmos.getFromGoneTransitionInteractor(),
+                () -> mKosmos.getFromLockscreenTransitionInteractor(),
                 () -> mKosmos.getSharedNotificationContainerInteractor(),
                 mTestScope);
 
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 f49dc98..dc7525c 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
@@ -55,6 +55,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -337,7 +338,9 @@
 
         // WHEN we want to unlock collapse
         mBiometricUnlockController.startWakeAndUnlock(
-                BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
+                BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
+                BiometricUnlockSource.FINGERPRINT_SENSOR
+        );
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(
                 /* strongAuth */ eq(false));
@@ -501,7 +504,7 @@
                         TransitionState.STARTED
                 )
         );
-        verify(mBiometricUnlockInteractor, never()).setBiometricUnlockState(anyInt());
+        verify(mBiometricUnlockInteractor, never()).setBiometricUnlockState(anyInt(), any());
 
         mBiometricUnlockController.consumeTransitionStepOnStartedKeyguardState(
                 new TransitionStep(
@@ -511,7 +514,7 @@
                         TransitionState.STARTED
                 )
         );
-        verify(mBiometricUnlockInteractor).setBiometricUnlockState(eq(MODE_NONE));
+        verify(mBiometricUnlockInteractor).setBiometricUnlockState(eq(MODE_NONE), eq(null));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 8e9840a..dfee737 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -178,6 +178,7 @@
                 keyguardTransitionInteractor,
                 () -> mKosmos.getSceneInteractor(),
                 () -> mKosmos.getFromGoneTransitionInteractor(),
+                () -> mKosmos.getFromLockscreenTransitionInteractor(),
                 () -> mKosmos.getSharedNotificationContainerInteractor(),
                 mTestScope);
         mViewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index c4ab943..405e3ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -250,7 +250,7 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_twoConnectionsOos_yes() =
+    fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
@@ -258,11 +258,13 @@
             val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
             val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
 
-            // WHEN all of the connections are OOS
+            // WHEN all of the connections are OOS and none are NTN
             i1.isInService.value = false
             i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
             i2.isInService.value = false
             i2.isEmergencyOnly.value = false
+            i2.isNonTerrestrial.value = false
 
             // THEN the value is propagated to this interactor
             assertThat(latest).isTrue()
@@ -270,7 +272,31 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_oneConnectionOos_yes() =
+    fun areAllConnectionsOutOfService_twoConnectionsOos_oneNtn_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 2 connections
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+
+            // WHEN all of the connections are OOS and one is NTN
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+            i2.isInService.value = false
+            i2.isEmergencyOnly.value = false
+
+            // sub2 is non terrestrial, consider it connected for the sake of the iconography
+            i2.isNonTerrestrial.value = true
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
@@ -280,6 +306,7 @@
             // WHEN all of the connections are OOS
             i1.isInService.value = false
             i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
 
             // THEN the value is propagated to this interactor
             assertThat(latest).isTrue()
@@ -287,7 +314,25 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_oneConnectionInService_no() =
+    fun areAllConnectionsOutOfService_oneConnectionOos_ntn_yes() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connection
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+            // WHEN all of the connections are OOS
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = true
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionInService_nonNtn_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
@@ -296,6 +341,7 @@
 
             // WHEN all of the connections are NOT OOS
             i1.isInService.value = true
+            i1.isNonTerrestrial.value = false
 
             // THEN the value is propagated to this interactor
             assertThat(latest).isFalse()
@@ -303,7 +349,24 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_twoConnectionsOneInService_no() =
+    fun areAllConnectionsOutOfService_oneConnectionInService_ntn_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connection
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+            // WHEN all of the connections are NOT OOS
+            i1.isInService.value = true
+            i1.isNonTerrestrial.value = true
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_twoConnectionsOneInService_nonNtn_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
@@ -313,7 +376,9 @@
 
             // WHEN at least 1 connection is NOT OOS.
             i1.isInService.value = false
+            i1.isNonTerrestrial.value = false
             i2.isInService.value = true
+            i2.isNonTerrestrial.value = false
 
             // THEN the value is propagated to this interactor
             assertThat(latest).isFalse()
@@ -321,7 +386,7 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_twoConnectionsInService_no() =
+    fun areAllConnectionsOutOfService_twoConnectionsInService_nonNtn_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 1e305d6..96a4049 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -19,6 +19,7 @@
 
 import android.graphics.Point
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DismissAction
@@ -101,7 +102,8 @@
     private val _isKeyguardGoingAway = MutableStateFlow(false)
     override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway
 
-    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    private val _biometricUnlockState =
+        MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null))
     override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
 
     private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null)
@@ -110,9 +112,6 @@
     private val _faceSensorLocation = MutableStateFlow<Point?>(null)
     override val faceSensorLocation: Flow<Point?> = _faceSensorLocation
 
-    private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null)
-    override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource
-
     private val _isQuickSettingsVisible = MutableStateFlow(false)
     override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
 
@@ -213,12 +212,19 @@
         _dozeAmount.value = dozeAmount
     }
 
-    override fun setBiometricUnlockState(value: BiometricUnlockModel) {
-        _biometricUnlockState.tryEmit(value)
+    override fun setBiometricUnlockState(
+        mode: BiometricUnlockMode,
+        source: BiometricUnlockSource?
+    ) {
+        _biometricUnlockState.tryEmit(BiometricUnlockModel(mode, source))
+    }
+
+    fun setBiometricUnlockState(mode: BiometricUnlockMode) {
+        setBiometricUnlockState(mode, BiometricUnlockSource.FINGERPRINT_SENSOR)
     }
 
     fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
-        _biometricUnlockSource.tryEmit(source)
+        setBiometricUnlockState(BiometricUnlockMode.NONE, source)
     }
 
     fun setFaceSensorLocation(location: Point?) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSmartspaceRepository.kt
new file mode 100644
index 0000000..42cb150
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSmartspaceRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyguard.data.repository
+
+import android.view.View.GONE
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeKeyguardSmartspaceRepository : KeyguardSmartspaceRepository {
+
+    private val _bcSmartspaceVisibility = MutableStateFlow(GONE)
+    override val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility
+    private val _isWeatherEnabled = MutableStateFlow(true)
+    override val isWeatherEnabled: StateFlow<Boolean> = _isWeatherEnabled
+
+    override fun setBcSmartspaceVisibility(visibility: Int) {
+        _bcSmartspaceVisibility.value = visibility
+    }
+
+    fun setIsWeatherEnabled(isEnabled: Boolean) {
+        _isWeatherEnabled.value = isEnabled
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt
index dc7103f..2949452 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryKosmos.kt
@@ -18,4 +18,4 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.keyguardSmartspaceRepository by Kosmos.Fixture { KeyguardSmartspaceRepository() }
+val Kosmos.keyguardSmartspaceRepository by Kosmos.Fixture { FakeKeyguardSmartspaceRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 2e751cc..02842cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -53,6 +53,7 @@
         shadeRepository: FakeShadeRepository = FakeShadeRepository(),
         sceneInteractor: SceneInteractor = mock(),
         fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
+        fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(),
         sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null,
         powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
         testScope: CoroutineScope = TestScope(),
@@ -98,6 +99,7 @@
                 keyguardTransitionInteractor = keyguardTransitionInteractor,
                 sceneInteractorProvider = { sceneInteractor },
                 fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
+                fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
                 sharedNotificationContainerInteractor = { sncInteractor },
                 applicationScope = testScope,
             ),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 9426718..81d8f0b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -18,20 +18,20 @@
 
 import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 
 val Kosmos.keyguardInteractor: KeyguardInteractor by
     Kosmos.Fixture {
         KeyguardInteractor(
             repository = keyguardRepository,
-            commandQueue = commandQueue,
+            commandQueue = fakeCommandQueue,
             powerInteractor = powerInteractor,
             bouncerRepository = keyguardBouncerRepository,
             configurationInteractor = configurationInteractor,
@@ -39,6 +39,7 @@
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             sceneInteractorProvider = { sceneInteractor },
             fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
+            fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
             sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor },
             applicationScope = testScope.backgroundScope,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index e44fa07..c951642 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -18,6 +18,12 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.ui.compose.GridLayout
 
 val Kosmos.gridLayoutTypeInteractor by
     Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) }
+
+val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
+    Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 0dbaaba..1893c30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -14,15 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.shared.model
+package com.android.systemui.qs.panels.domain.interactor
 
-import dagger.MapKey
-import kotlin.reflect.KClass
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
 
-/**
- * Dagger map key to associate a [GridLayoutType] with its
- * [com.android.systemui.qs.panels.ui.compose.GridLayout].
- */
-@Retention(AnnotationRetention.RUNTIME)
-@MapKey
-annotation class GridLayoutTypeKey(val value: KClass<out GridLayoutType>)
+val Kosmos.infiniteGridLayout by Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 9851f04..5fd8762 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -17,9 +17,10 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
 import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor
-import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 
@@ -27,8 +28,9 @@
     Kosmos.Fixture {
         TileGridViewModel(
             gridLayoutTypeInteractor,
-            mapOf(Pair(InfiniteGridLayoutType::class.java, InfiniteGridLayout())),
+            gridLayoutMap,
             currentTilesInteractor,
-            iconTilesInteractor
+            InfiniteGridLayout(iconTilesInteractor),
+            applicationCoroutineScope,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
new file mode 100644
index 0000000..45ec032
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.overlayShadeViewModel: OverlayShadeViewModel by
+    Kosmos.Fixture {
+        OverlayShadeViewModel(
+            applicationScope = applicationCoroutineScope,
+            sceneInteractor = sceneInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
+        )
+    }
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
index 7dc197e..7a3142b 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java
@@ -28,8 +28,10 @@
  * Add this with a fully-specified method name (e.g. {@code "com.package.Class.methodName"})
  * of a callback to get a callback at the class load time.
  *
- * The method must be {@code public static} with a single argument that takes
- * {@link Class}.
+ * The method must be {@code public static} with a single argument that takes {@link Class}.
+ *
+ * Typically, this is used with {@link #LIBANDROID_LOADING_HOOK}, which will load the necessary
+ * native libraries.
  *
  * @hide
  */
@@ -37,4 +39,10 @@
 @Retention(RetentionPolicy.CLASS)
 public @interface RavenwoodClassLoadHook {
     String value();
+
+    /**
+     * Class load hook that loads <code>libandroid_runtime</code>.
+     */
+    public static String LIBANDROID_LOADING_HOOK
+            = "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded";
 }
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 9057d16..96b7057 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -44,6 +44,14 @@
 
     public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
 
+    /**
+     * Extra strings needed to pass to register_android_graphics_classes().
+     *
+     * `android.graphics.Graphics` is not actually a class, so we can't use the same initialization
+     * strategy than the "normal" classes. So we just hardcode it here.
+     */
+    public static final String GRAPHICS_EXTRA_INIT_PARAMS = ",android.graphics.Graphics";
+
     private static String sInitialDir = new File("").getAbsolutePath();
 
     static {
@@ -98,7 +106,6 @@
     private static void loadFrameworkNativeCode() {
         // This is called from class-initializers, so no synchronization is needed.
         if (sLoadFrameworkNativeCodeCalled) {
-            // This method has already been called before.s
             return;
         }
         sLoadFrameworkNativeCodeCalled = true;
@@ -112,7 +119,8 @@
         }
 
         if (SKIP_LOADING_LIBANDROID) {
-            log("Skip loading " + LIBANDROID_RUNTIME_NAME);
+            log("Skip loading native runtime.");
+            return;
         }
 
         // Make sure these properties are not set.
@@ -121,27 +129,39 @@
         ensurePropertyNotSet(KEYBOARD_PATHS);
         ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES);
 
-        // Tell libandroid what JNI to use.
-        final var jniClasses = getCoreNativeClassesToUse();
-        if (jniClasses.isEmpty()) {
-            log("No classes require JNI methods, skip loading " + LIBANDROID_RUNTIME_NAME);
+        // Load the libraries, if needed.
+        final var libanrdoidClasses = getClassesWithNativeMethods(sLibandroidClasses);
+        final var libhwuiClasses = getClassesWithNativeMethods(sLibhwuiClasses);
+        if (libanrdoidClasses.isEmpty() && libhwuiClasses.isEmpty()) {
+            log("No classes require JNI methods, skip loading native runtime.");
             return;
         }
-        setProperty(CORE_NATIVE_CLASSES, jniClasses);
-        setProperty(GRAPHICS_NATIVE_CLASSES, "");
+        setProperty(CORE_NATIVE_CLASSES, libanrdoidClasses);
+        setProperty(GRAPHICS_NATIVE_CLASSES, libhwuiClasses + GRAPHICS_EXTRA_INIT_PARAMS);
 
+        log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libanrdoidClasses + "' and '"
+                + libhwuiClasses + "'");
         RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
     }
 
     /**
-     * Classes with native methods that are backed by `libandroid_runtime`.
+     * Classes with native methods that are backed by libandroid_runtime.
      *
-     * At runtime, we check if these classes have any methods, and if so, we'll have
-     * `libandroid_runtime` register the native functions.
+     * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
      */
-    private static final Class<?>[] sClassesWithLibandroidNativeMethods = {
+    private static final Class<?>[] sLibandroidClasses = {
             android.util.Log.class,
-            android.os.Parcel.class,
+    };
+
+    /**
+     * Classes with native methods that are backed by libhwui.
+     *
+     * See frameworks/base/libs/hwui/apex/LayoutlibLoader.cpp
+     */
+    private static final Class<?>[] sLibhwuiClasses = {
+            android.graphics.Interpolator.class,
+            android.graphics.Matrix.class,
+            android.graphics.Path.class,
     };
 
     /**
@@ -157,17 +177,15 @@
     }
 
     /**
-     * Create a list of classes as comma-separated that require JNI methods to be set up.
-     *
-     * <p>This list is used by frameworks/base/core/jni/LayoutlibLoader.cpp to decide
-     * what JNI methods to set up.
+     * Create a list of classes as comma-separated that require JNI methods to be set up from
+     * a given class list, ignoring classes with no native methods.
      */
-    private static String getCoreNativeClassesToUse() {
+    private static String getClassesWithNativeMethods(Class<?>[] classes) {
         final var coreNativeClassesToLoad = new ArrayList<String>();
 
-        for (var clazz : sClassesWithLibandroidNativeMethods) {
+        for (var clazz : classes) {
             if (hasNativeMethod(clazz)) {
-                log("Class %s has native methods", clazz);
+                log("Class %s has native methods", clazz.getCanonicalName());
                 coreNativeClassesToLoad.add(clazz.getName());
             }
         }
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 243e224..e452299 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -236,7 +236,11 @@
 
 android.accounts.Account
 
+android.graphics.Bitmap$Config
 android.graphics.Insets
+android.graphics.Interpolator
+android.graphics.Matrix
+android.graphics.Path
 android.graphics.Point
 android.graphics.PointF
 android.graphics.Rect
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ad869a1..2bece6c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -852,7 +852,7 @@
     }
 
     private void registerBroadcastReceivers() {
-        mPackageMonitor = new PackageMonitor() {
+        mPackageMonitor = new PackageMonitor(true) {
             @Override
             public void onSomePackagesChanged() {
                 if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 522aa67..e830523 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2292,11 +2292,32 @@
         args.arg4 = requestId;
         args.argi1 = widget.appWidgetId;
 
+        if (updateViews != null && updateViews.isLegacyListRemoteViews()) {
+            mCallbackHandler.obtainMessage(
+                    CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET_DEFERRED,
+                    args).sendToTarget();
+            return;
+        }
+
         mCallbackHandler.obtainMessage(
                 CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
                 args).sendToTarget();
     }
 
+    private void handleNotifyUpdateAppWidgetDeferred(Host host, IAppWidgetHost callbacks,
+            int appWidgetId, long requestId) {
+        try {
+            Slog.d(TAG, "Trying to notify widget update deferred for id: " + appWidgetId);
+            callbacks.updateAppWidgetDeferred(appWidgetId);
+            host.lastWidgetUpdateSequenceNo = requestId;
+        } catch (RemoteException re) {
+            synchronized (mLock) {
+                Slog.e(TAG, "Widget host dead: " + host.id, re);
+                host.callbacks = null;
+            }
+        }
+    }
+
     private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
             int appWidgetId, RemoteViews views, long requestId) {
         try {
@@ -4277,6 +4298,7 @@
         public static final int MSG_NOTIFY_PROVIDERS_CHANGED = 3;
         public static final int MSG_NOTIFY_VIEW_DATA_CHANGED = 4;
         public static final int MSG_NOTIFY_APP_WIDGET_REMOVED = 5;
+        public static final int MSG_NOTIFY_UPDATE_APP_WIDGET_DEFERRED = 6;
 
         public CallbackHandler(Looper looper) {
             super(looper, null, false);
@@ -4340,6 +4362,17 @@
                     handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId,
                             requestId);
                 } break;
+
+                case MSG_NOTIFY_UPDATE_APP_WIDGET_DEFERRED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    Host host = (Host) args.arg1;
+                    IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+                    long requestId = (Long) args.arg4;
+                    final int appWidgetId = args.argi1;
+                    args.recycle();
+
+                    handleNotifyUpdateAppWidgetDeferred(host, callbacks, appWidgetId, requestId);
+                } break;
             }
         }
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 4547bd6..743086e 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -187,9 +187,9 @@
         fout.println(indent + "VirtualCameraController:");
         indent += indent;
         synchronized (mCameras) {
-            fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size());
+            fout.println(indent + "Registered cameras: " + mCameras.size());
             for (CameraDescriptor descriptor : mCameras.values()) {
-                fout.printf("%s token: %s\n", indent, descriptor.mConfig);
+                fout.println(indent + " token: " +  descriptor.mConfig);
             }
         }
     }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8ee560b..3ff0504 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -242,6 +242,7 @@
         "com.android.sysprop.watchdog",
         "securebox",
         "apache-commons-math",
+        "battery_saver_flag_lib",
         "notification_flags_lib",
         "power_hint_flags_lib",
         "biometrics_flags_lib",
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 28af222..d26ba48 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -26,6 +26,8 @@
 import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
 import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
 
+import static com.android.media.audio.Flags.portToPiidSimplification;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -152,6 +154,8 @@
             new HashMap<Integer, AudioPlaybackConfiguration>();
 
     @GuardedBy("mPlayerLock")
+    private final SparseIntArray mPiidToPortId = new SparseIntArray();
+    @GuardedBy("mPlayerLock")
     private final SparseIntArray mPortIdToPiid = new SparseIntArray();
 
     private final Context mContext;
@@ -369,7 +373,11 @@
             sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
 
             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
-                mPortIdToPiid.put(eventValue, piid);
+                if (portToPiidSimplification()) {
+                    mPiidToPortId.put(piid, eventValue);
+                } else {
+                    mPortIdToPiid.put(eventValue, piid);
+                }
                 return;
             } else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                 for (Integer uidInteger: mBannedUids) {
@@ -430,10 +438,20 @@
         }
 
         synchronized (mPlayerLock) {
-            int piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
-            if (piid == PLAYER_PIID_INVALID) {
-                Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
-                return;
+            int piid;
+            if (portToPiidSimplification()) {
+                int idxOfPiid = mPiidToPortId.indexOfValue(portId);
+                if (idxOfPiid < 0) {
+                    Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
+                    return;
+                }
+                piid = mPiidToPortId.keyAt(idxOfPiid);
+            } else {
+                piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
+                if (piid == PLAYER_PIID_INVALID) {
+                    Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
+                    return;
+                }
             }
             final AudioPlaybackConfiguration apc = mPlayers.get(piid);
             if (apc == null) {
@@ -489,10 +507,14 @@
                 change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
                         AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
 
-                // remove all port ids mapped to the released player
-                int portIdx;
-                while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
-                    mPortIdToPiid.removeAt(portIdx);
+                if (portToPiidSimplification()) {
+                    mPiidToPortId.delete(piid);
+                } else {
+                    // remove all port ids mapped to the released player
+                    int portIdx;
+                    while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
+                        mPortIdToPiid.removeAt(portIdx);
+                    }
                 }
 
                 if (change && mDoNotLogPiidList.contains(piid)) {
@@ -511,10 +533,17 @@
                 new EventLogger.StringEvent(
                         "clear port id to piid map"));
         synchronized (mPlayerLock) {
-            if (DEBUG) {
-                Log.v(TAG, "clear port id to piid map:\n" + mPortIdToPiid);
+            if (portToPiidSimplification()) {
+                if (DEBUG) {
+                    Log.v(TAG, "clear piid to portId map:\n" + mPiidToPortId);
+                }
+                mPiidToPortId.clear();
+            } else {
+                if (DEBUG) {
+                    Log.v(TAG, "clear port id to piid map:\n" + mPortIdToPiid);
+                }
+                mPortIdToPiid.clear();
             }
-            mPortIdToPiid.clear();
         }
     }
 
@@ -674,12 +703,21 @@
                 pw.print(" " + piid);
             }
             pw.println("\n");
-            // portId to piid mappings:
-            pw.println("\n  current portId to piid map:");
-            for (int i = 0; i < mPortIdToPiid.size(); ++i) {
-                pw.println(
-                        "  portId: " + mPortIdToPiid.keyAt(i) + " piid: " + mPortIdToPiid.valueAt(
-                                i));
+            if (portToPiidSimplification()) {
+                // portId to piid mappings:
+                pw.println("\n  current piid to portId map:");
+                for (int i = 0; i < mPiidToPortId.size(); ++i) {
+                    pw.println(
+                            "  piid: " + mPiidToPortId.keyAt(i) + " portId: "
+                                    + mPiidToPortId.valueAt(i));
+                }
+            } else {
+                // portId to piid mappings:
+                pw.println("\n  current portId to piid map:");
+                for (int i = 0; i < mPortIdToPiid.size(); ++i) {
+                    pw.println("  portId: " + mPortIdToPiid.keyAt(i) + " piid: "
+                            + mPortIdToPiid.valueAt(i));
+                }
             }
             pw.println("\n");
             // log
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 92fd9cb..712dcee 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -14,3 +14,10 @@
   description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
   bug: "294254230"
 }
+
+flag {
+  name: "mandatory_biometrics"
+  namespace: "biometrics_framework"
+  description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
+  bug: "322081563"
+}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 9c020a7..d9c3ab8 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -181,7 +181,7 @@
     private long mLightSensorEnableTime;
 
     // The currently accepted nominal ambient light level.
-    private float mAmbientLux;
+    private float mAmbientLux = INVALID_LUX;
 
     // The last calculated ambient light level (long time window).
     private float mSlowAmbientLux;
@@ -434,23 +434,23 @@
      * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
      * the initial brightness in doze mode.
      */
-    public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
+    public float getAutomaticScreenBrightnessBasedOnLastUsedLux(
             BrightnessEvent brightnessEvent) {
-        if (mLastObservedLux == INVALID_LUX) {
+        float lastUsedLux = mAmbientLux;
+        if (lastUsedLux == INVALID_LUX) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
 
-        float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
+        float brightness = mCurrentBrightnessMapper.getBrightness(lastUsedLux,
                 mForegroundAppPackageName, mForegroundAppCategory);
         if (shouldApplyDozeScaleFactor()) {
             brightness *= mDozeScaleFactor;
         }
 
         if (brightnessEvent != null) {
-            brightnessEvent.setLux(mLastObservedLux);
+            brightnessEvent.setLux(lastUsedLux);
             brightnessEvent.setRecommendedBrightness(brightness);
             brightnessEvent.setFlags(brightnessEvent.getFlags()
-                    | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
                     | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
             brightnessEvent.setAutoBrightnessMode(getMode());
         }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a6335e3..eeacc53 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1268,7 +1268,7 @@
         return mAmbientLightSensor;
     }
 
-    SensorData getScreenOffBrightnessSensor() {
+    public SensorData getScreenOffBrightnessSensor() {
         return mScreenOffBrightnessSensor;
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c5d8686..d7a7dd4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -851,6 +851,13 @@
 
         mHandler.postAtTime(() -> {
             boolean changed = false;
+
+            if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
+                changed = true;
+                mIsEnabled = isEnabled;
+                mIsInTransition = isInTransition;
+            }
+
             if (mDisplayDevice != device) {
                 changed = true;
                 mDisplayDevice = device;
@@ -875,11 +882,6 @@
                         mThermalBrightnessThrottlingDataId,
                         mUniqueDisplayId);
             }
-            if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
-                changed = true;
-                mIsEnabled = isEnabled;
-                mIsInTransition = isInTransition;
-            }
 
             mIsDisplayInternal = isDisplayInternal;
             // using local variables here, when mBrightnessThrottler is removed,
@@ -1113,29 +1115,29 @@
                     mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits,
                     mBrightnessClamperController);
-            mDisplayBrightnessController.setAutomaticBrightnessController(
-                    mAutomaticBrightnessController);
-
-            mAutomaticBrightnessStrategy
-                    .setAutomaticBrightnessController(mAutomaticBrightnessController);
+            mDisplayBrightnessController.setUpAutoBrightness(
+                    mAutomaticBrightnessController, mSensorManager, mDisplayDeviceConfig, mHandler,
+                    defaultModeBrightnessMapper, mIsEnabled, mLeadDisplayId);
             mBrightnessEventRingBuffer =
                     new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
-
-            if (mScreenOffBrightnessSensorController != null) {
-                mScreenOffBrightnessSensorController.stop();
-                mScreenOffBrightnessSensorController = null;
-            }
-            loadScreenOffBrightnessSensor();
-            int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
-            if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
-                mScreenOffBrightnessSensorController =
-                        mInjector.getScreenOffBrightnessSensorController(
-                                mSensorManager,
-                                mScreenOffBrightnessSensor,
-                                mHandler,
-                                SystemClock::uptimeMillis,
-                                sensorValueToLux,
-                                defaultModeBrightnessMapper);
+            if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+                if (mScreenOffBrightnessSensorController != null) {
+                    mScreenOffBrightnessSensorController.stop();
+                    mScreenOffBrightnessSensorController = null;
+                }
+                loadScreenOffBrightnessSensor();
+                int[] sensorValueToLux =
+                        mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
+                if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
+                    mScreenOffBrightnessSensorController =
+                            mInjector.getScreenOffBrightnessSensorController(
+                                    mSensorManager,
+                                    mScreenOffBrightnessSensor,
+                                    mHandler,
+                                    SystemClock::uptimeMillis,
+                                    sensorValueToLux,
+                                    defaultModeBrightnessMapper);
+                }
             }
         } else {
             mUseSoftwareAutoBrightnessConfig = false;
@@ -1255,7 +1257,8 @@
             mPowerState = null;
         }
 
-        if (mScreenOffBrightnessSensorController != null) {
+        if (!mFlags.isRefactorDisplayPowerControllerEnabled()
+                && mScreenOffBrightnessSensorController != null) {
             mScreenOffBrightnessSensorController.stop();
         }
 
@@ -1351,15 +1354,17 @@
         if (displayBrightnessState.getBrightnessEvent() != null) {
             mTempBrightnessEvent.copyFrom(displayBrightnessState.getBrightnessEvent());
         }
-        // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
-        // doesn't yet have a valid lux value to use with auto-brightness.
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController
-                    .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
-                    && mIsEnabled && (state == Display.STATE_OFF
-                    || (state == Display.STATE_DOZE
-                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
-                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+        if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+            // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+            // doesn't yet have a valid lux value to use with auto-brightness.
+            if (mScreenOffBrightnessSensorController != null) {
+                mScreenOffBrightnessSensorController
+                        .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+                        && mIsEnabled && (state == Display.STATE_OFF
+                        || (state == Display.STATE_DOZE
+                        && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+                        && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+            }
         }
 
         // Take note if the short term model was already active before applying the current
@@ -1392,15 +1397,6 @@
         boolean updateScreenBrightnessSetting =
                 displayBrightnessState.shouldUpdateScreenBrightnessSetting();
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
-        // Apply auto-brightness.
-        // All the conditions inside this if block will be moved to AutomaticBrightnessStrategy
-        if (mFlags.isRefactorDisplayPowerControllerEnabled()
-                && displayBrightnessState.getBrightnessReason().getReason()
-                        == BrightnessReason.REASON_AUTOMATIC) {
-            if (mScreenOffBrightnessSensorController != null) {
-                mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
-            }
-        }
 
         if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
             // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
@@ -1452,7 +1448,7 @@
                     && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
                 // Use the auto-brightness curve and the last observed lux
                 rawBrightnessState = mAutomaticBrightnessController
-                        .getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                        .getAutomaticScreenBrightnessBasedOnLastUsedLux(
                                 mTempBrightnessEvent);
             } else {
                 rawBrightnessState = getDozeBrightnessForOffload();
@@ -1473,19 +1469,23 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
         }
 
-        // The ALS is not available yet - use the screen off sensor to determine the initial
-        // brightness
-        if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
-                && mScreenOffBrightnessSensorController != null) {
-            rawBrightnessState =
-                    mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
-            brightnessState = rawBrightnessState;
-            if (BrightnessUtils.isValidBrightnessValue(brightnessState)) {
-                brightnessState = clampScreenBrightness(brightnessState);
-                updateScreenBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness()
-                        != brightnessState;
-                mBrightnessReasonTemp.setReason(
-                        BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
+        if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+            // The ALS is not available yet - use the screen off sensor to determine the initial
+            // brightness
+            if (Float.isNaN(brightnessState)
+                    && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
+                    && mScreenOffBrightnessSensorController != null) {
+                rawBrightnessState =
+                        mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
+                brightnessState = rawBrightnessState;
+                if (BrightnessUtils.isValidBrightnessValue(brightnessState)) {
+                    brightnessState = clampScreenBrightness(brightnessState);
+                    updateScreenBrightnessSetting =
+                            mDisplayBrightnessController.getCurrentBrightness()
+                                    != brightnessState;
+                    mBrightnessReasonTemp.setReason(
+                            BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
index 42defac..0a884c9 100644
--- a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
+++ b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
@@ -25,8 +25,6 @@
 import android.os.PowerManager;
 import android.util.IndentingPrintWriter;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.io.PrintWriter;
 
 /**
@@ -79,7 +77,10 @@
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }
 
-    void setLightSensorEnabled(boolean enabled) {
+    /**
+     * Changes the state of the associated light sensor
+     */
+    public void setLightSensorEnabled(boolean enabled) {
         if (enabled && !mRegistered) {
             // Wait until we get an event from the sensor indicating ready.
             mRegistered = mSensorManager.registerListener(this, mLightSensor,
@@ -92,11 +93,17 @@
         }
     }
 
-    void stop() {
+    /**
+     * Stops the associated sensor, and cleans up the state
+     */
+    public void stop() {
         setLightSensorEnabled(false);
     }
 
-    float getAutomaticScreenBrightness() {
+    /**
+     * Gets the automatic screen brightness based on the ambient lux
+     */
+    public float getAutomaticScreenBrightness() {
         if (mLastSensorValue < 0 || mLastSensorValue >= mSensorValueToLux.length
                 || (!mRegistered
                 && mClock.uptimeMillis() - mSensorDisableTime > SENSOR_VALUE_VALID_TIME_MILLIS)) {
@@ -121,8 +128,7 @@
     }
 
     /** Functional interface for providing time. */
-    @VisibleForTesting
-    interface Clock {
+    public interface Clock {
         /**
          * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
          */
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 6a88a76..aa17df6 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -18,7 +18,9 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.SensorManager;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.PowerManager;
 import android.util.IndentingPrintWriter;
@@ -31,6 +33,8 @@
 import com.android.server.display.BrightnessMappingStrategy;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -103,7 +107,8 @@
     // The controller for the automatic brightness level.
     // TODO(b/265415257): Move to the automatic brightness strategy
     @Nullable
-    private AutomaticBrightnessController mAutomaticBrightnessController;
+    @VisibleForTesting
+    AutomaticBrightnessController mAutomaticBrightnessController;
 
     /**
      * The constructor of DisplayBrightnessController.
@@ -332,14 +337,16 @@
     }
 
     /**
-     * Set the {@link AutomaticBrightnessController} which is needed to perform nit-to-float-scale
-     * conversion.
-     * @param automaticBrightnessController The ABC
+     * Sets up the auto brightness and the relevant state for the associated display
      */
-    public void setAutomaticBrightnessController(
-            AutomaticBrightnessController automaticBrightnessController) {
-        mAutomaticBrightnessController = automaticBrightnessController;
-        loadNitBasedBrightnessSetting();
+    public void setUpAutoBrightness(AutomaticBrightnessController automaticBrightnessController,
+            SensorManager sensorManager,
+            DisplayDeviceConfig displayDeviceConfig, Handler handler,
+            BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled,
+            int leadDisplayId) {
+        setAutomaticBrightnessController(automaticBrightnessController);
+        setUpAutoBrightnessFallbackStrategy(sensorManager, displayDeviceConfig, handler,
+                brightnessMappingStrategy, isEnabled, leadDisplayId);
     }
 
     /**
@@ -404,6 +411,17 @@
         if (mBrightnessSetting != null) {
             mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
         }
+        AutoBrightnessFallbackStrategy autoBrightnessFallbackStrategy =
+                getAutoBrightnessFallbackStrategy();
+        if (autoBrightnessFallbackStrategy != null) {
+            autoBrightnessFallbackStrategy.stop();
+        }
+    }
+
+    private AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
+        synchronized (mLock) {
+            return mDisplayBrightnessStrategySelector.getAutoBrightnessFallbackStrategy();
+        }
     }
 
     /**
@@ -484,6 +502,33 @@
     }
 
     /**
+     * Set the {@link AutomaticBrightnessController} which is needed to perform nit-to-float-scale
+     * conversion.
+     * @param automaticBrightnessController The ABC
+     */
+    @VisibleForTesting
+    void setAutomaticBrightnessController(
+            AutomaticBrightnessController automaticBrightnessController) {
+        mAutomaticBrightnessController = automaticBrightnessController;
+        getAutomaticBrightnessStrategy()
+                .setAutomaticBrightnessController(automaticBrightnessController);
+        loadNitBasedBrightnessSetting();
+    }
+
+    private void setUpAutoBrightnessFallbackStrategy(SensorManager sensorManager,
+            DisplayDeviceConfig displayDeviceConfig, Handler handler,
+            BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled,
+            int leadDisplayId) {
+        AutoBrightnessFallbackStrategy autoBrightnessFallbackStrategy =
+                getAutoBrightnessFallbackStrategy();
+        if (autoBrightnessFallbackStrategy != null) {
+            autoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(
+                    sensorManager, displayDeviceConfig, handler, brightnessMappingStrategy,
+                    isEnabled, leadDisplayId);
+        }
+    }
+
+    /**
      * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
      */
     private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 282083f..22a21a6 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
@@ -81,10 +82,14 @@
     @Nullable
     private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
 
+    @Nullable
+    private final AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy;
+
     // A collective representation of all the strategies that the selector is aware of. This is
     // non null, but the strategies this is tracking can be null
     @NonNull
-    private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies;
+    @VisibleForTesting
+    final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies;
 
     @NonNull
     private final DisplayManagerFlags mDisplayManagerFlags;
@@ -120,6 +125,9 @@
         mAutomaticBrightnessStrategy =
                 (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled())
                         ? mAutomaticBrightnessStrategy1 : mAutomaticBrightnessStrategy2;
+        mAutoBrightnessFallbackStrategy = (mDisplayManagerFlags
+                .isRefactorDisplayPowerControllerEnabled())
+                ? injector.getAutoBrightnessFallbackStrategy() : null;
         if (flags.isDisplayOffloadEnabled()) {
             mOffloadBrightnessStrategy = injector
                     .getOffloadBrightnessStrategy(mDisplayManagerFlags);
@@ -129,7 +137,8 @@
         mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy,
                 mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy,
                 mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy,
-                mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy};
+                mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy,
+                mAutoBrightnessFallbackStrategy};
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
         mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -168,6 +177,8 @@
                 && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
                 mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
             displayBrightnessStrategy = mOffloadBrightnessStrategy;
+        } else if (isAutoBrightnessFallbackStrategyValid()) {
+            displayBrightnessStrategy = mAutoBrightnessFallbackStrategy;
         }
 
         if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
@@ -210,6 +221,11 @@
         return mAllowAutoBrightnessWhileDozingConfig;
     }
 
+    @Nullable
+    public AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
+        return mAutoBrightnessFallbackStrategy;
+    }
+
     /**
      * Dumps the state of this class.
      */
@@ -229,6 +245,13 @@
         }
     }
 
+    private boolean isAutoBrightnessFallbackStrategyValid() {
+        return mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()
+                && mAutoBrightnessFallbackStrategy != null
+                && getAutomaticBrightnessStrategy().shouldUseAutoBrightness()
+                && mAutoBrightnessFallbackStrategy.isValid();
+    }
+
     private boolean isAutomaticBrightnessStrategyValid(
             StrategySelectionRequest strategySelectionRequest) {
         mAutomaticBrightnessStrategy1.setAutoBrightnessState(
@@ -245,12 +268,13 @@
             DisplayBrightnessStrategy selectedDisplayBrightnessStrategy,
             StrategySelectionRequest strategySelectionRequest) {
         return new StrategySelectionNotifyRequest(
-                        strategySelectionRequest.getDisplayPowerRequest(),
+                strategySelectionRequest.getDisplayPowerRequest(),
                 strategySelectionRequest.getTargetDisplayState(),
                 selectedDisplayBrightnessStrategy,
                 strategySelectionRequest.getLastUserSetScreenBrightness(),
                 strategySelectionRequest.isUserSetBrightnessChanged(),
-                isAllowAutoBrightnessWhileDozingConfig());
+                isAllowAutoBrightnessWhileDozingConfig(),
+                getAutomaticBrightnessStrategy().shouldUseAutoBrightness());
     }
 
     private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
@@ -319,5 +343,9 @@
                 DisplayManagerFlags displayManagerFlags) {
             return new OffloadBrightnessStrategy(displayManagerFlags);
         }
+
+        AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
+            return new AutoBrightnessFallbackStrategy(/* injector= */ null);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
index 6e6c972..bfa90e2 100644
--- a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -45,18 +45,22 @@
 
     // True if light sensor is to be used to automatically determine doze screen brightness.
     private final boolean mAllowAutoBrightnessWhileDozingConfig;
+    // True if the auto brightness is enabled in the settings
+    private final boolean mIsAutoBrightnessEnabled;
 
     public StrategySelectionNotifyRequest(
             DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState,
             DisplayBrightnessStrategy displayBrightnessStrategy,
             float lastUserSetScreenBrightness,
-            boolean userSetBrightnessChanged, boolean allowAutoBrightnessWhileDozingConfig) {
+            boolean userSetBrightnessChanged, boolean allowAutoBrightnessWhileDozingConfig,
+            boolean isAutoBrightnessEnabled) {
         mDisplayPowerRequest = displayPowerRequest;
         mTargetDisplayState = targetDisplayState;
         mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
         mLastUserSetScreenBrightness = lastUserSetScreenBrightness;
         mUserSetBrightnessChanged = userSetBrightnessChanged;
         mAllowAutoBrightnessWhileDozingConfig = allowAutoBrightnessWhileDozingConfig;
+        mIsAutoBrightnessEnabled = isAutoBrightnessEnabled;
     }
 
     public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
@@ -76,14 +80,15 @@
                 && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged()
                 && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness()
                 && mAllowAutoBrightnessWhileDozingConfig
-                == other.isAllowAutoBrightnessWhileDozingConfig();
+                == other.isAllowAutoBrightnessWhileDozingConfig()
+                && mIsAutoBrightnessEnabled == other.isAutoBrightnessEnabled();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSelectedDisplayBrightnessStrategy, mDisplayPowerRequest,
                 mTargetDisplayState, mUserSetBrightnessChanged, mLastUserSetScreenBrightness,
-                mAllowAutoBrightnessWhileDozingConfig);
+                mAllowAutoBrightnessWhileDozingConfig, mIsAutoBrightnessEnabled);
     }
 
     public float getLastUserSetScreenBrightness() {
@@ -106,6 +111,10 @@
         return mAllowAutoBrightnessWhileDozingConfig;
     }
 
+    public boolean isAutoBrightnessEnabled() {
+        return mIsAutoBrightnessEnabled;
+    }
+
     /**
      * A utility to stringify a StrategySelectionNotifyRequest
      */
@@ -116,6 +125,7 @@
                 + " mSelectedDisplayBrightnessStrategy=" + mSelectedDisplayBrightnessStrategy
                 + " mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness
                 + " mUserSetBrightnessChanged=" + mUserSetBrightnessChanged
-                + " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig;
+                + " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig
+                + " mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled;
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
new file mode 100644
index 0000000..16bf177f
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2024 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.brightness.strategy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.BrightnessMappingStrategy;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.ScreenOffBrightnessSensorController;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategyExecutionRequest;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+import com.android.server.display.layout.Layout;
+import com.android.server.display.utils.SensorUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * This strategy is used when the screen has just turned ON, with auto-brightness ON but there is
+ * no valid lux values available yet. In such a case, if configured, we set the brightness state
+ * from this
+ */
+public final class AutoBrightnessFallbackStrategy implements DisplayBrightnessStrategy {
+
+    @Nullable
+    private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+    @VisibleForTesting
+    @Nullable
+    Sensor mScreenOffBrightnessSensor;
+
+    // Indicates if the associated LogicalDisplay is enabled or not.
+    private boolean mIsEnabled;
+
+    // Represents if the associated display is a lead display or not. If not, the variable
+    // represents the lead display ID
+    private int mLeadDisplayId;
+
+    @NonNull
+    private final Injector mInjector;
+
+    public AutoBrightnessFallbackStrategy(Injector injector) {
+        mInjector = (injector == null) ? new RealInjector() : injector;
+    }
+
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            StrategyExecutionRequest strategyExecutionRequest) {
+        assert mScreenOffBrightnessSensorController != null;
+        float brightness = mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
+        return new DisplayBrightnessState.Builder()
+                .setBrightness(brightness)
+                .setSdrBrightness(brightness)
+                .setBrightnessReason(brightnessReason)
+                .setDisplayBrightnessStrategyName(getName())
+                .setShouldUpdateScreenBrightnessSetting(brightness
+                        != strategyExecutionRequest.getCurrentScreenBrightness())
+                .build();
+    }
+
+    @NonNull
+    @Override
+    public String getName() {
+        return "AutoBrightnessFallbackStrategy";
+    }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("AutoBrightnessFallbackStrategy:");
+        writer.println("  mLeadDisplayId=" + mLeadDisplayId);
+        writer.println("  mIsEnabled=" + mIsEnabled);
+        if (mScreenOffBrightnessSensorController != null) {
+            IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+            mScreenOffBrightnessSensorController.dump(ipw);
+        }
+    }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        if (mScreenOffBrightnessSensorController != null) {
+            int targetDisplayState = strategySelectionNotifyRequest.getTargetDisplayState();
+            mScreenOffBrightnessSensorController.setLightSensorEnabled(
+                    strategySelectionNotifyRequest.isAutoBrightnessEnabled() && mIsEnabled
+                            && (targetDisplayState == Display.STATE_OFF
+                            || (targetDisplayState == Display.STATE_DOZE
+                            && !strategySelectionNotifyRequest
+                            .isAllowAutoBrightnessWhileDozingConfig()))
+                            && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+        }
+    }
+
+    /**
+     * Gets the associated ScreenOffBrightnessSensorController, controlling the brightness when
+     * auto-brightness is enabled, but the lux is not valid yet.
+     */
+    public ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController() {
+        return mScreenOffBrightnessSensorController;
+    }
+
+    /**
+     * Sets up the auto brightness fallback sensor
+     */
+    public void setupAutoBrightnessFallbackSensor(SensorManager sensorManager,
+            DisplayDeviceConfig displayDeviceConfig, Handler handler,
+            BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled,
+            int leadDisplayId) {
+        mIsEnabled = isEnabled;
+        mLeadDisplayId = leadDisplayId;
+        if (mScreenOffBrightnessSensorController != null) {
+            mScreenOffBrightnessSensorController.stop();
+            mScreenOffBrightnessSensorController = null;
+        }
+        loadScreenOffBrightnessSensor(sensorManager, displayDeviceConfig);
+        int[] sensorValueToLux = displayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
+        if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
+            mScreenOffBrightnessSensorController =
+                    mInjector.getScreenOffBrightnessSensorController(
+                            sensorManager,
+                            mScreenOffBrightnessSensor,
+                            handler,
+                            SystemClock::uptimeMillis,
+                            sensorValueToLux,
+                            brightnessMappingStrategy);
+        }
+    }
+
+    /**
+     * Stops the associated ScreenOffBrightnessSensorController responsible for managing the
+     * brightness when this strategy is selected
+     */
+    public void stop() {
+        if (mScreenOffBrightnessSensorController != null) {
+            mScreenOffBrightnessSensorController.stop();
+        }
+    }
+
+    /**
+     * Checks if the strategy is valid, based on its internal state. Note that there can still be
+     * external factors like auto-brightness not being enabled because of which this strategy is not
+     * selected
+     */
+    public boolean isValid() {
+        return mScreenOffBrightnessSensorController != null
+                && BrightnessUtils.isValidBrightnessValue(
+                mScreenOffBrightnessSensorController.getAutomaticScreenBrightness());
+    }
+
+    private void loadScreenOffBrightnessSensor(SensorManager sensorManager,
+            DisplayDeviceConfig displayDeviceConfig) {
+        mScreenOffBrightnessSensor = mInjector.getScreenOffBrightnessSensor(sensorManager,
+                displayDeviceConfig);
+    }
+
+
+    @VisibleForTesting
+    interface Injector {
+        Sensor getScreenOffBrightnessSensor(SensorManager sensorManager,
+                DisplayDeviceConfig displayDeviceConfig);
+
+        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+                SensorManager sensorManager,
+                Sensor lightSensor,
+                Handler handler,
+                ScreenOffBrightnessSensorController.Clock clock,
+                int[] sensorValueToLux,
+                BrightnessMappingStrategy brightnessMapper);
+    }
+
+    static class RealInjector implements Injector {
+        @Override
+        public Sensor getScreenOffBrightnessSensor(SensorManager sensorManager,
+                DisplayDeviceConfig displayDeviceConfig) {
+            return SensorUtils.findSensor(sensorManager,
+                    displayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
+        }
+
+        @Override
+        public ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+                SensorManager sensorManager, Sensor lightSensor, Handler handler,
+                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
+                BrightnessMappingStrategy brightnessMapper) {
+            return new ScreenOffBrightnessSensorController(
+                    sensorManager, lightSensor, handler, clock, sensorValueToLux, brightnessMapper);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 5c4fa842..2305228 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -360,11 +360,11 @@
      * @param brightnessEvent Event object to populate with details about why the specific
      *                        brightness was chosen.
      */
-    public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
+    public float getAutomaticScreenBrightnessBasedOnLastUsedLux(
             BrightnessEvent brightnessEvent) {
         float brightness = (mAutomaticBrightnessController != null)
                 ? mAutomaticBrightnessController
-                .getAutomaticScreenBrightnessBasedOnLastObservedLux(brightnessEvent)
+                .getAutomaticScreenBrightnessBasedOnLastUsedLux(brightnessEvent)
                 : PowerManager.BRIGHTNESS_INVALID_FLOAT;
         adjustAutomaticBrightnessStateIfValid(brightness);
         return brightness;
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
index 25e8b23..58670c9 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java
@@ -279,11 +279,11 @@
      * @param brightnessEvent Event object to populate with details about why the specific
      *                        brightness was chosen.
      */
-    public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
+    public float getAutomaticScreenBrightnessBasedOnLastUsedLux(
             BrightnessEvent brightnessEvent) {
         float brightness = (mAutomaticBrightnessController != null)
                 ? mAutomaticBrightnessController
-                .getAutomaticScreenBrightnessBasedOnLastObservedLux(brightnessEvent)
+                .getAutomaticScreenBrightnessBasedOnLastUsedLux(brightnessEvent)
                 : PowerManager.BRIGHTNESS_INVALID_FLOAT;
         adjustAutomaticBrightnessStateIfValid(brightness);
         return brightness;
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
index d494be5..e91de37 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
@@ -123,8 +123,7 @@
      * Returns the system-gender to be backed up as a data-blob.
      */
     public byte[] getSystemBackupPayload(int userId) {
-        int gender = mGrammaticalGenderService.getSystemGrammaticalGender(mAttributionSource,
-                userId);
+        int gender = mGrammaticalGenderService.getSystemGrammaticalGender(userId);
         return intToByteArray(gender);
     }
 
@@ -167,7 +166,7 @@
         BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
     }
 
-    private byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) {
+    private static byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) {
         try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
              final ObjectOutputStream objStream = new ObjectOutputStream(out)) {
             objStream.writeObject(pkgGenderInfo);
@@ -178,22 +177,22 @@
         }
     }
 
-    private byte[] intToByteArray(final int gender) {
+    private static byte[] intToByteArray(final int gender) {
         ByteBuffer bb = ByteBuffer.allocate(4);
         bb.putInt(gender);
         return bb.array();
     }
 
-    private int convertByteArrayToInt(byte[] intBytes) {
+    private static int convertByteArrayToInt(byte[] intBytes) {
         ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
         return byteBuffer.getInt();
     }
 
-    private HashMap<String, Integer> readFromByteArray(byte[] payload) {
+    private static HashMap<String, Integer> readFromByteArray(byte[] payload) {
         HashMap<String, Integer> data = new HashMap<>();
 
-        try (ByteArrayInputStream byteIn = new ByteArrayInputStream(payload);
-             ObjectInputStream in = new ObjectInputStream(byteIn)) {
+        try (var byteIn = new ByteArrayInputStream(payload);
+                var in = new ObjectInputStream(byteIn)) {
             data = (HashMap<String, Integer>) in.readObject();
         } catch (IOException | ClassNotFoundException e) {
             Log.e(TAG, "cannot convert payload to HashMap.", e);
@@ -205,10 +204,10 @@
     private void cleanStagedDataForOldEntries() {
         for (int i = 0; i < mCache.size(); i++) {
             int userId = mCache.keyAt(i);
-            StagedData stagedData = mCache.get(userId);
+            StagedData stagedData = mCache.valueAt(userId);
             if (stagedData.mCreationTimeMillis
                     < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
-                mCache.remove(userId);
+                mCache.removeAt(i--);
             }
         }
     }
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 2816d08..7eb971c 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -16,7 +16,6 @@
 
 package com.android.server.grammaticalinflection;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 
@@ -41,7 +40,7 @@
     public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
 
     /**
-     * Get the current system grammatical gender of privileged application.
+     * Get the current system grammatical gender for the particular user.
      *
      * @return the value of grammatical gender
      *
@@ -50,18 +49,25 @@
     public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
 
     /**
-     * Retrieve the system grammatical gender.
+     * Get the final merged value of the global grammatical gender, user- or devsettings-set.
      *
      * @return the value of grammatical gender
      *
      */
-    public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender(
-            @NonNull Configuration configuration);
+    public abstract @Configuration.GrammaticalGender int mergedFinalSystemGrammaticalGender();
+
+    /**
+     * Get the grammatical gender from developer settings global override.
+     *
+     * @return the value of grammatical gender
+     */
+    public abstract
+            @Configuration.GrammaticalGender int getGrammaticalGenderFromDeveloperSettings();
 
     /**
      * Whether the package can get the system grammatical gender or not.
      */
-    public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName);
+    public abstract boolean canGetSystemGrammaticalGender(int uid);
 
 
     /**
@@ -74,4 +80,3 @@
      */
     public abstract void applyRestoredSystemPayload(byte[] payload, int userId);
 }
-
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 93a71b9..e242164 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -17,7 +17,6 @@
 package com.android.server.grammaticalinflection;
 
 import static android.app.Flags.systemTermsOfAddressEnabled;
-import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 
 import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
 
@@ -43,6 +42,7 @@
 import android.util.SparseIntArray;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -126,7 +126,7 @@
 
         @Override
         public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
-            isCallerAllowed();
+            enforceCallerPermissions();
             GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
                     userId);
         }
@@ -138,18 +138,16 @@
                         + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
             }
             return checkSystemTermsOfAddressIsEnabled()
-                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
-                    attributionSource, userId)
-                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+                    : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
         @Override
         public int peekSystemGrammaticalGenderByUserId(AttributionSource attributionSource,
                 int userId) {
             return canGetSystemGrammaticalGender(attributionSource)
-                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
-                    attributionSource, userId)
-                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+                    : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
         @Override
@@ -163,11 +161,10 @@
 
     private final class GrammaticalInflectionManagerInternalImpl
             extends GrammaticalInflectionManagerInternal {
-
         @Override
         @Nullable
         public byte[] getBackupPayload(int userId) {
-            isCallerAllowed();
+            enforceCallerPermissions();
             return mBackupHelper.getBackupPayload(userId);
         }
 
@@ -179,7 +176,7 @@
         @Override
         @Nullable
         public byte[] getSystemBackupPayload(int userId) {
-            isCallerAllowed();
+            enforceCallerPermissions();
             return mBackupHelper.getSystemBackupPayload(userId);
         }
 
@@ -191,30 +188,35 @@
         @Override
         public int getSystemGrammaticalGender(int userId) {
             return checkSystemTermsOfAddressIsEnabled()
-                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
-                    mContext.getAttributionSource(), userId)
-                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+                    : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
         @Override
-        public int retrieveSystemGrammaticalGender(Configuration configuration) {
+        public int mergedFinalSystemGrammaticalGender() {
             int systemGrammaticalGender = getSystemGrammaticalGender(mContext.getUserId());
             // Retrieve the grammatical gender from system property, set it into
             // configuration which will get updated later if the grammatical gender raw value of
             // current configuration is {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
-            if (configuration.getGrammaticalGenderRaw()
-                    == Configuration.GRAMMATICAL_GENDER_UNDEFINED
-                    || systemGrammaticalGender <= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
-                systemGrammaticalGender = SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
-                        Configuration.GRAMMATICAL_GENDER_UNDEFINED);
+            if (systemGrammaticalGender == Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+                systemGrammaticalGender = getGrammaticalGenderFromDeveloperSettings();
             }
-            return systemGrammaticalGender;
+            return systemGrammaticalGender == Configuration.GRAMMATICAL_GENDER_UNDEFINED
+                    ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : systemGrammaticalGender;
         }
 
         @Override
-        public boolean canGetSystemGrammaticalGender(int uid, String packageName) {
-            AttributionSource attributionSource = new AttributionSource.Builder(
-                    uid).setPackageName(packageName).build();
+        public int getGrammaticalGenderFromDeveloperSettings() {
+            return SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
+                    Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
+        }
+
+        @Override
+        public boolean canGetSystemGrammaticalGender(int uid) {
+            if (uid == Process.SYSTEM_UID) {
+                return true;
+            }
+            var attributionSource = new AttributionSource.Builder(uid).build();
             return GrammaticalInflectionService.this.canGetSystemGrammaticalGender(
                     attributionSource);
         }
@@ -225,7 +227,7 @@
                 mActivityTaskManagerInternal.getApplicationConfig(appPackageName, userId);
 
         if (appConfig == null || appConfig.mGrammaticalGender == null) {
-            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+            return Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         } else {
             return appConfig.mGrammaticalGender;
         }
@@ -239,9 +241,10 @@
                         userId);
 
         if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
-            if (preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+            if (preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
                 Log.d(TAG, "Clearing the user's grammatical gender setting");
-                updater.setGrammaticalGender(GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
+                updater.setGrammaticalGender(
+                        Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
             }
             return;
         }
@@ -250,49 +253,48 @@
         FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED,
                 FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__OTHERS,
                 uid,
-                gender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
-                preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+                gender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+                preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
 
         updater.setGrammaticalGender(gender).commit();
     }
 
     protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
-        Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
-        if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
-                grammaticalGender)) {
-            throw new IllegalArgumentException("Unknown grammatical gender");
-        }
-
-        if (!checkSystemTermsOfAddressIsEnabled()) {
-            if (grammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED) {
-                return;
+        try {
+            if (!checkSystemTermsOfAddressIsEnabled()) {
+                return; // Nothing to do, and the flag can't get flipped at the runtime.
             }
-            Log.d(TAG, "Clearing the system grammatical gender setting");
-            grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
-        }
 
-        synchronized (mLock) {
+            Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
+            if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+                    grammaticalGender)) {
+                throw new IllegalArgumentException("Unknown grammatical gender");
+            }
+
             final File file = getGrammaticalGenderFile(userId);
-            final AtomicFile atomicFile = new AtomicFile(file);
-            FileOutputStream stream = null;
-            try {
-                stream = atomicFile.startWrite();
-                stream.write(toXmlByteArray(grammaticalGender, stream));
-                atomicFile.finishWrite(stream);
-                mGrammaticalGenderCache.put(userId, grammaticalGender);
-            } catch (IOException e) {
-                Log.e(TAG, "Failed to write file " + atomicFile, e);
-                if (stream != null) {
-                    atomicFile.failWrite(stream);
+            synchronized (mLock) {
+                final AtomicFile atomicFile = new AtomicFile(file);
+                FileOutputStream stream = null;
+                try {
+                    stream = atomicFile.startWrite();
+                    stream.write(toXmlByteArray(grammaticalGender, stream));
+                    atomicFile.finishWrite(stream);
+                    mGrammaticalGenderCache.put(userId, grammaticalGender);
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to write file " + atomicFile, e);
+                    if (stream != null) {
+                        atomicFile.failWrite(stream);
+                    }
+                    throw new RuntimeException(e);
                 }
-                throw new RuntimeException(e);
             }
+            updateConfiguration(grammaticalGender, userId);
+        } finally {
+            Trace.endSection();
         }
-        updateConfiguration(grammaticalGender, userId);
-        Trace.endSection();
     }
 
-    private void updateConfiguration(int grammaticalGender, int userId) {
+    private static void updateConfiguration(int grammaticalGender, int userId) {
         try {
             Configuration config = new Configuration();
             int preValue = config.getGrammaticalGender();
@@ -301,54 +303,47 @@
             FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED,
                     FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__SYSTEM,
                     userId,
-                    grammaticalGender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
-                    preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+                    grammaticalGender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+                    preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
             GrammaticalInflectionBackupHelper.notifyBackupManager();
         } catch (RemoteException e) {
             Log.w(TAG, "Can not update configuration", e);
         }
     }
 
-    public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
-        String packageName = attributionSource.getPackageName();
-        if (packageName == null) {
-            Log.d(TAG, "Package name is null.");
-            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
-        }
-
+    /**
+     * Returns the system global grammatical gender value for the requested user.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public int getSystemGrammaticalGender(int userId) {
         synchronized (mLock) {
             int grammaticalGender = mGrammaticalGenderCache.get(userId);
-            return grammaticalGender < 0 ? GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
+            return grammaticalGender < 0
+                    ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
         }
     }
 
-    private File getGrammaticalGenderFile(int userId) {
+    private static File getGrammaticalGenderFile(int userId) {
         final File dir = new File(Environment.getDataSystemCeDirectory(userId),
                 TAG_GRAMMATICAL_INFLECTION);
         return new File(dir, USER_SETTINGS_FILE_NAME);
     }
 
-    private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
-
-        try {
-            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
-            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-            out.startDocument(/* encoding= */ null, /* standalone= */ true);
-            out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
-            out.attributeInt(null, ATTR_NAME, grammaticalGender);
-            out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
-            out.endDocument();
-
-            return outputStream.toByteArray();
-        } catch (IOException e) {
-            return null;
-        }
+    private static byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream)
+            throws IOException {
+        var outputStream = new ByteArrayOutputStream();
+        TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        out.startDocument(/* encoding= */ null, /* standalone= */ true);
+        out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+        out.attributeInt(null, ATTR_NAME, grammaticalGender);
+        out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+        out.endDocument();
+        return outputStream.toByteArray();
     }
 
-    private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+    private static int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
             throws IOException, XmlPullParserException {
-
         XmlUtils.nextElement(parser);
         while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
             String tagName = parser.getName();
@@ -359,20 +354,20 @@
             }
         }
 
-        return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        return Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
     }
 
-    private void isCallerAllowed() {
+    private void enforceCallerPermissions() {
         int callingUid = Binder.getCallingUid();
         if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
                 && callingUid != Process.ROOT_UID) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.CHANGE_CONFIGURATION,
-                    "Caller must be system, shell, root or has CHANGE_CONFIGURATION permission.");
+                    "Caller must be system, shell, root or hold CHANGE_CONFIGURATION permission.");
         }
     }
 
-    private boolean checkSystemTermsOfAddressIsEnabled() {
+    private static boolean checkSystemTermsOfAddressIsEnabled() {
         if (!systemTermsOfAddressEnabled()) {
             Log.d(TAG, "The flag must be enabled to allow calling the API.");
             return false;
@@ -387,25 +382,31 @@
 
     @Override
     public void onUserUnlocked(TargetUser user) {
+        if (!checkSystemTermsOfAddressIsEnabled()) {
+            return;
+        }
         IoThread.getHandler().post(() -> {
-            int userId = user.getUserIdentifier();
+            final int userId = user.getUserIdentifier();
             final File file = getGrammaticalGenderFile(userId);
+            final int grammaticalGender;
             synchronized (mLock) {
                 if (!file.exists()) {
                     Log.d(TAG, "User " + userId + " doesn't have the grammatical gender file.");
                     return;
                 }
-                if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
-                    try (FileInputStream in = new FileInputStream(file)) {
-                        final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-                        int grammaticalGender = getGrammaticalGenderFromXml(parser);
-                        mGrammaticalGenderCache.put(userId, grammaticalGender);
-                        updateConfiguration(grammaticalGender, userId);
-                    } catch (IOException | XmlPullParserException e) {
-                        Log.e(TAG, "Failed to parse XML configuration from " + file, e);
-                    }
+                if (mGrammaticalGenderCache.indexOfKey(userId) >= 0) {
+                    return;
+                }
+                try (FileInputStream in = new FileInputStream(file)) {
+                    final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                    grammaticalGender = getGrammaticalGenderFromXml(parser);
+                    mGrammaticalGenderCache.put(userId, grammaticalGender);
+                } catch (IOException | XmlPullParserException e) {
+                    Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+                    return;
                 }
             }
+            updateConfiguration(grammaticalGender, userId);
         });
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f6dfc9e..cd2c037 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -350,7 +350,7 @@
      * {@link HdmiCecNetwork} only.
      *
      * @return CEC physical address of the device. The range of success address
-     *         is between 0x0000 and 0xFFFF. If failed it returns -1
+     *         is between 0x0000 and 0xFFFE. If failed it returns INVALID_PHYSICAL_ADDRESS.
      */
     @ServiceThreadOnly
     int getPhysicalAddress() {
@@ -1315,7 +1315,7 @@
                     hdmiPortInfo[i] = new HdmiPortInfo.Builder(
                             portInfo.portId,
                             portInfo.type,
-                            portInfo.physicalAddress)
+                            Short.toUnsignedInt(portInfo.physicalAddress))
                             .setCecSupported(portInfo.cecSupported)
                             .setMhlSupported(false)
                             .setArcSupported(portInfo.arcSupported)
@@ -1512,7 +1512,7 @@
                     hdmiPortInfo[i] = new HdmiPortInfo.Builder(
                             portInfo.portId,
                             portInfo.type,
-                            portInfo.physicalAddress)
+                            Short.toUnsignedInt(portInfo.physicalAddress))
                             .setCecSupported(portInfo.cecSupported)
                             .setMhlSupported(false)
                             .setArcSupported(portInfo.arcSupported)
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 54e1217..d10e192 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -867,6 +867,60 @@
                         }
                     }
                 }, mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        reportFeatures(true);
+                    }
+                },
+                mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        reportFeatures(false);
+                    }
+                },
+                mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        reportFeatures(false);
+                    }
+                },
+                mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        reportFeatures(false);
+                    }
+                },
+                mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        reportFeatures(false);
+                    }
+                },
+                mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(
+                HdmiControlManager
+                        .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        reportFeatures(false);
+                    }
+                },
+                mServiceThreadExecutor);
 
         if (isTvDevice()) {
             mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
@@ -968,6 +1022,21 @@
         }
     }
 
+    /** Helper method for sending feature discovery command */
+    private void reportFeatures(boolean isTvDeviceSetting) {
+        // check if tv device is enabled for tv device specific RC profile setting
+        if (isTvDeviceSetting) {
+            if (isTvDeviceEnabled()) {
+                tv().reportFeatures();
+            }
+        } else { // check for source device setting
+            HdmiCecLocalDeviceSource source = isAudioSystemDevice() ? audioSystem() : playback();
+            if (source != null) {
+                source.reportFeatures();
+            }
+        }
+    }
+
     /**
      * Returns the initial power status used when the HdmiControlService starts.
      */
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
index 8949427..bf6633e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
@@ -124,8 +124,7 @@
                 return historySize(pw);
         }
 
-        getErrPrintWriter().println("Unhandled command: " + cmd);
-        return 1;
+        return handleDefaultCommands(cmd);
     }
 
     private int deviceSelect(PrintWriter pw) throws RemoteException {
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index ef52d2a..e28939b 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -986,7 +986,7 @@
     }
 
     private void startTrackingPackageChanges() {
-        final PackageMonitor monitor = new PackageMonitor() {
+        final PackageMonitor monitor = new PackageMonitor(true) {
 
             @Override
             public void onPackageUpdateStarted(@NonNull String packageName, int uid) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 44a200e..8985022e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -382,6 +382,16 @@
     @MultiUserUnawareField
     Future<?> mImeDrawsImeNavBarResLazyInitFuture;
 
+    private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void dumpToProto(ProtoOutputStream proto, @Nullable byte[] icProto) {
+            dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+        }
+    };
+
     static class SessionState {
         final ClientState mClient;
         final IInputMethodInvoker mMethod;
@@ -471,11 +481,6 @@
         return mBindingController.getSelectedMethodId();
     }
 
-    @GuardedBy("ImfLock.class")
-    private void setSelectedMethodIdLocked(@Nullable String selectedMethodId) {
-        mBindingController.setSelectedMethodId(selectedMethodId);
-    }
-
     /**
      * The current binding sequence number, incremented every time there is
      * a new bind performed.
@@ -816,6 +821,8 @@
                     }
                 } else if (stylusHandwritingEnabledUri.equals(uri)) {
                     InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
+                    InputMethodManager
+                            .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
                 } else {
                     boolean enabledChanged = false;
                     String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
@@ -934,6 +941,10 @@
          */
         private ArrayList<String> mDataClearedPackages = new ArrayList<>();
 
+        private MyPackageMonitor() {
+            super(true);
+        }
+
         @GuardedBy("ImfLock.class")
         void clearKnownImePackageNamesLocked() {
             mKnownImePackageNames.clear();
@@ -1526,6 +1537,20 @@
                     + " currentUserId=" + mCurrentUserId);
         }
 
+        // Clean up stuff for mCurrentUserId, which soon becomes the previous user.
+
+        // TODO(b/338461930): Check if this is still necessary or not.
+        onUnbindCurrentMethodByReset();
+
+        // Note that in b/197848765 we want to see if we can keep the binding alive for better
+        // profile switching.
+        mBindingController.unbindCurrentMethod();
+        // TODO(b/325515685): No need to do this once BindingController becomes per-user.
+        mBindingController.setSelectedMethodId(null);
+        unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
+
+        // Hereafter we start initializing things for "newUserId".
+
         maybeInitImeNavbarConfigLocked(newUserId);
 
         // ContentObserver should be registered again when the user is changed
@@ -1547,10 +1572,6 @@
         // IME for that user.
         final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
 
-        // The mSystemReady flag is set during boot phase,
-        // and user switch would not happen at that time.
-        resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
-
         final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
         postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
         if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
@@ -2497,8 +2518,9 @@
 
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
-        setSelectedMethodIdLocked(null);
+        mBindingController.setSelectedMethodId(null);
         // Callback before clean-up binding states.
+        // TODO(b/338461930): Check if this is still necessary or not.
         onUnbindCurrentMethodByReset();
         mBindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
@@ -3077,7 +3099,7 @@
             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
             // because mCurMethodId is stored as a history in
             // setSelectedInputMethodAndSubtypeLocked().
-            setSelectedMethodIdLocked(id);
+            mBindingController.setSelectedMethodId(id);
 
             if (mActivityManagerInternal.isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3099,7 +3121,7 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
-                "InputMethodManagerService#showSoftInput");
+                "InputMethodManagerService#showSoftInput", mDumper);
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
                 ImeTracker.forLogging().onFailed(
@@ -3198,7 +3220,7 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
         try {
             ImeTracing.getInstance().triggerManagerServiceDump(
-                    "InputMethodManagerService#startStylusHandwriting");
+                    "InputMethodManagerService#startStylusHandwriting", mDumper);
             int uid = Binder.getCallingUid();
             synchronized (ImfLock.class) {
                 if (!acceptingDelegation) {
@@ -3401,7 +3423,14 @@
         mBindingController.setCurrentMethodVisible();
         final IInputMethodInvoker curMethod = getCurMethodLocked();
         ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
-        if (curMethod != null) {
+        final boolean readyToDispatchToIme;
+        if (Flags.deferShowSoftInputUntilSessionCreation()) {
+            readyToDispatchToIme =
+                    curMethod != null && mCurClient != null && mCurClient.mCurSession != null;
+        } else {
+            readyToDispatchToIme = curMethod != null;
+        }
+        if (readyToDispatchToIme) {
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
             mCurStatsToken = null;
 
@@ -3426,7 +3455,7 @@
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
-                "InputMethodManagerService#hideSoftInput");
+                "InputMethodManagerService#hideSoftInput", mDumper);
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
                 if (isInputShownLocked()) {
@@ -3564,7 +3593,7 @@
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                     "IMMS.startInputOrWindowGainedFocus");
             ImeTracing.getInstance().triggerManagerServiceDump(
-                    "InputMethodManagerService#startInputOrWindowGainedFocus");
+                    "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
             final InputBindResult result;
             synchronized (ImfLock.class) {
                 // If the system is not yet ready, we shouldn't be running third party code.
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 2c14532..c5e2bb8 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -291,9 +291,15 @@
 
     private void setOldSettingForBackworkCompatibility(boolean isActive) {
         // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it.
-        // They should switch to calling #isFrpActive().
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.SECURE_FRP_MODE, isActive ? 1 : 0);
+        // They should switch to calling #isFrpActive().  Clear calling ID since this can happen
+        // during an app call.
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    Settings.Global.SECURE_FRP_MODE, isActive ? 1 : 0);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
     }
 
     private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) {
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 306f77d..3862b79 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -734,11 +734,11 @@
                     .addCategory(Intent.CATEGORY_BROWSABLE)
                     .build();
 
-    /** SMS and MMS can be handled by the private profile or by the parent user. */
+    /** SMS and MMS are always handled in the main user. */
     private static final DefaultCrossProfileIntentFilter SMS_MMS_PRIVATE_PROFILE =
             new DefaultCrossProfileIntentFilter.Builder(
                     DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
-                    ONLY_IF_NO_MATCH_FOUND,
+                    SKIP_CURRENT_PROFILE,
                     /* letsPersonalDataIntoProfile= */ false)
                     .addAction(Intent.ACTION_VIEW)
                     .addAction(Intent.ACTION_SENDTO)
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3f5ec06..82902d4 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -50,7 +50,6 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.IShortcutService;
 import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutChangeCallback;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -152,7 +151,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
@@ -322,11 +320,12 @@
 
     private final Handler mHandler;
 
-    private final CopyOnWriteArrayList<ShortcutChangeListener> mListeners =
-            new CopyOnWriteArrayList<>();
+    @GuardedBy("mServiceLock")
+    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
 
-    private final CopyOnWriteArrayList<ShortcutChangeCallback> mShortcutChangeCallbacks =
-            new CopyOnWriteArrayList<>();
+    @GuardedBy("mServiceLock")
+    private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
+            new ArrayList<>(1);
 
     private final AtomicLong mRawLastResetTime = new AtomicLong(0);
 
@@ -1842,11 +1841,18 @@
             @UserIdInt final int userId) {
         return () -> {
             try {
-                if (!isUserUnlockedL(userId)) {
-                    return;
+                final ArrayList<ShortcutChangeListener> copy;
+                synchronized (mServiceLock) {
+                    if (!isUserUnlockedL(userId)) {
+                        return;
+                    }
+
+                    copy = new ArrayList<>(mListeners);
                 }
                 // Note onShortcutChanged() needs to be called with the system service permissions.
-                mListeners.forEach(listener -> listener.onShortcutChanged(packageName, userId));
+                for (int i = copy.size() - 1; i >= 0; i--) {
+                    copy.get(i).onShortcutChanged(packageName, userId);
+                }
             } catch (Exception ignore) {
             }
         };
@@ -1861,17 +1867,22 @@
         final UserHandle user = UserHandle.of(userId);
         injectPostToHandler(() -> {
             try {
-                if (!isUserUnlockedL(userId)) {
-                    return;
+                final ArrayList<LauncherApps.ShortcutChangeCallback> copy;
+                synchronized (mServiceLock) {
+                    if (!isUserUnlockedL(userId)) {
+                        return;
+                    }
+
+                    copy = new ArrayList<>(mShortcutChangeCallbacks);
                 }
-                mShortcutChangeCallbacks.forEach(callback -> {
+                for (int i = copy.size() - 1; i >= 0; i--) {
                     if (!CollectionUtils.isEmpty(changedList)) {
-                        callback.onShortcutsAddedOrUpdated(packageName, changedList, user);
+                        copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user);
                     }
                     if (!CollectionUtils.isEmpty(removedList)) {
-                        callback.onShortcutsRemoved(packageName, removedList, user);
+                        copy.get(i).onShortcutsRemoved(packageName, removedList, user);
                     }
-                });
+                }
             } catch (Exception ignore) {
             }
         });
@@ -3414,13 +3425,17 @@
 
         @Override
         public void addListener(@NonNull ShortcutChangeListener listener) {
-            mListeners.add(Objects.requireNonNull(listener));
+            synchronized (mServiceLock) {
+                mListeners.add(Objects.requireNonNull(listener));
+            }
         }
 
         @Override
         public void addShortcutChangeCallback(
                 @NonNull LauncherApps.ShortcutChangeCallback callback) {
-            mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
+            synchronized (mServiceLock) {
+                mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/power/batterysaver/Android.bp b/services/core/java/com/android/server/power/batterysaver/Android.bp
new file mode 100644
index 0000000..0b04345
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/Android.bp
@@ -0,0 +1,14 @@
+aconfig_declarations {
+    name: "battery_saver_flag",
+    package: "com.android.server.power.batterysaver",
+    container: "system",
+    srcs: [
+        "*.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "battery_saver_flag_lib",
+    aconfig_declarations: "battery_saver_flag",
+    sdk_version: "system_current",
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index c8cb92b..9a4c60d 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.BatterySaverPolicyConfig;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -97,6 +98,9 @@
     private static final String TAG = "BatterySaverStateMachine";
     private static final String DYNAMIC_MODE_NOTIF_CHANNEL_ID = "dynamic_mode_notification";
     private static final String BATTERY_SAVER_NOTIF_CHANNEL_ID = "battery_saver_channel";
+    private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+    private static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_args";
+    private static final String PREFERENCE_KEY_BATTERY_SAVER_SCHEDULER = "battery_saver_schedule";
     private static final int DYNAMIC_MODE_NOTIFICATION_ID = 1992;
     private static final int STICKY_AUTO_DISABLED_NOTIFICATION_ID = 1993;
     private final Object mLock;
@@ -831,7 +835,11 @@
         // Handle triggering the notification to show/hide when appropriate
         if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON
                 || intReason == BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_ON) {
-            triggerDynamicModeNotification();
+            if (Flags.updateAutoTurnOnNotificationStringAndAction()) {
+                triggerDynamicModeNotificationV2();
+            } else {
+                triggerDynamicModeNotification();
+            }
         } else if (!enable) {
             hideDynamicModeNotification();
         }
@@ -862,6 +870,31 @@
     }
 
     @VisibleForTesting
+    void triggerDynamicModeNotificationV2() {
+        // The current lock is the PowerManager lock, which sits very low in the service lock
+        // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+        runOnBgThread(() -> {
+            NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+            ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+                    R.string.dynamic_mode_notification_channel_name);
+
+            // The bundle is used for highlighting a settings item when launching the settings page.
+            final var highlightBundle = new Bundle(1 /* capacity */);
+            highlightBundle.putString(
+                    EXTRA_FRAGMENT_ARG_KEY, PREFERENCE_KEY_BATTERY_SAVER_SCHEDULER);
+
+            manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
+                    buildNotificationV2(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+                            R.string.dynamic_mode_notification_title_v2,
+                            R.string.dynamic_mode_notification_summary_v2,
+                            Settings.ACTION_BATTERY_SAVER_SETTINGS,
+                            0L /* timeoutMs */,
+                            highlightBundle),
+                    UserHandle.ALL);
+        });
+    }
+
+    @VisibleForTesting
     void triggerStickyDisabledNotification() {
         if (!mBatterySaverTurnedOffNotificationEnabled) {
             return;
@@ -915,6 +948,32 @@
                 .build();
     }
 
+    private Notification buildNotificationV2(@NonNull String channelId, @StringRes int titleId,
+            @StringRes int summaryId, @NonNull String intentAction, long timeoutMs,
+            @NonNull Bundle highlightBundle) {
+        Resources res = mContext.getResources();
+        Intent intent = new Intent(intentAction)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                .putExtra(EXTRA_SHOW_FRAGMENT_TITLE, highlightBundle);
+
+        PendingIntent batterySaverIntent = PendingIntent.getActivity(
+                mContext, 0 /* requestCode */, intent,
+                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+        final String title = res.getString(titleId);
+        final String summary = res.getString(summaryId);
+
+        return new Notification.Builder(mContext, channelId)
+                .setSmallIcon(R.drawable.ic_battery)
+                .setContentTitle(title)
+                .setContentText(summary)
+                .setContentIntent(batterySaverIntent)
+                .setStyle(new Notification.BigTextStyle().bigText(summary))
+                .setOnlyAlertOnce(true)
+                .setAutoCancel(true)
+                .setTimeoutAfter(timeoutMs)
+                .build();
+    }
+
     private void hideDynamicModeNotification() {
         hideNotification(DYNAMIC_MODE_NOTIFICATION_ID);
     }
diff --git a/services/core/java/com/android/server/power/batterysaver/flags.aconfig b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
new file mode 100644
index 0000000..fa29dc1
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.server.power.batterysaver"
+container: "system"
+
+flag {
+  name: "update_auto_turn_on_notification_string_and_action"
+  namespace: "battery_saver"
+  description: "Improve the string and hightligh settings item for battery saver auto-turn-on notification"
+  bug: "336960905"
+  metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
index 7126cb5..7c2ce64 100644
--- a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
+++ b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java
@@ -110,7 +110,7 @@
         final EnabledComponentsObserver o = new EnabledComponentsObserver(context, settingName,
                 servicePermission, serviceName, lock, listeners);
 
-        PackageMonitor packageMonitor = new PackageMonitor() {
+        PackageMonitor packageMonitor = new PackageMonitor(true) {
             @Override
             public void onSomePackagesChanged() {
                 o.onPackagesChanged();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f1ba755..d20b3b2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1346,6 +1346,10 @@
     }
 
     class MyPackageMonitor extends PackageMonitor {
+        private MyPackageMonitor() {
+            super(true);
+        }
+
         @Override
         public void onPackageUpdateFinished(String packageName, int uid) {
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7d057a9..e814f17 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7846,10 +7846,12 @@
 
     @Override
     void prepareSurfaces() {
+        final boolean isDecorSurfaceBoosted =
+                getTask() != null && getTask().isDecorSurfaceBoosted();
         final boolean show = (isVisible()
                 // Ensure that the activity content is hidden when the decor surface is boosted to
                 // prevent UI redressing attack.
-                && !getTask().isDecorSurfaceBoosted())
+                && !isDecorSurfaceBoosted)
                 || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
                         | ANIMATION_TYPE_PREDICT_BACK);
 
@@ -9492,6 +9494,12 @@
         return false;
     }
 
+    @Override
+    protected boolean setOverrideGender(Configuration requestsTmpConfig, int gender) {
+        return WindowProcessController.applyConfigGenderOverride(
+                requestsTmpConfig, gender, mAtmService.mGrammaticalManagerInternal, getUid());
+    }
+
     @VisibleForTesting
     @Override
     Rect getAnimationBounds(int appRootTaskClipMode) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fc05d17..f3e1dfb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -949,7 +949,7 @@
         }
 
         configuration.setGrammaticalGender(
-                mGrammaticalManagerInternal.retrieveSystemGrammaticalGender(configuration));
+                mGrammaticalManagerInternal.mergedFinalSystemGrammaticalGender());
 
         synchronized (mGlobalLock) {
             mForceResizableActivities = forceResizable;
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 31754bf..efd5202 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -534,8 +534,8 @@
                 nightMode);
         boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
                 locales);
-        boolean newGenderSet = (gender != null) && setOverrideGender(mRequestsTmpConfig,
-                gender);
+        boolean newGenderSet = setOverrideGender(mRequestsTmpConfig,
+                gender == null ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : gender);
         if (newNightModeSet || newLocalesSet || newGenderSet) {
             onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
         }
@@ -577,14 +577,11 @@
      *
      * @return true if the grammatical gender has been changed.
      */
-    private boolean setOverrideGender(Configuration requestsTmpConfig,
+    protected boolean setOverrideGender(Configuration requestsTmpConfig,
             @Configuration.GrammaticalGender int gender) {
-        if (mRequestedOverrideConfiguration.getGrammaticalGender() == gender) {
-            return false;
-        } else {
-            requestsTmpConfig.setGrammaticalGender(gender);
-            return true;
-        }
+        // Noop, only ActivityRecord and WindowProcessController have enough knowledge about the
+        // app to apply gender correctly.
+        return false;
     }
 
     public boolean isActivityTypeDream() {
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 23127ac..9d597ea 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -169,6 +169,8 @@
                         LocaleOverlayHelper.combineLocalesIfOverlayExists(
                         modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()),
                         modifiedRecord.mGrammaticalGender);
+            } else {
+                container.applyAppSpecificConfig(null, null, null);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index fd1b5be..1c00fbb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -84,6 +84,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.Watchdog;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
 import com.android.server.wm.ActivityTaskManagerService.HotPath;
 
 import java.io.IOException;
@@ -324,8 +325,6 @@
      */
     private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
 
-    private final boolean mCanUseSystemGrammaticalGender;
-
     public WindowProcessController(@NonNull ActivityTaskManagerService atm,
             @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
             @NonNull WindowProcessListener listener) {
@@ -349,9 +348,6 @@
         mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()
                 && (isSysUiPackage || mAtm.isCallerRecents(uid));
 
-        mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
-                && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
-                mInfo.packageName);
         onConfigurationChanged(atm.getGlobalConfiguration());
         mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName);
     }
@@ -1612,11 +1608,6 @@
             return;
         }
 
-        if (mCanUseSystemGrammaticalGender) {
-            config.setGrammaticalGender(
-                    mAtm.mGrammaticalManagerInternal.getSystemGrammaticalGender(mUserId));
-        }
-
         if (mPauseConfigurationDispatchCount > 0) {
             mHasPendingConfigurationChange = true;
             return;
@@ -2139,4 +2130,34 @@
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         mListener.dumpDebug(proto, fieldId);
     }
+
+    @Override
+    protected boolean setOverrideGender(Configuration requestsTmpConfig, int gender) {
+        return applyConfigGenderOverride(requestsTmpConfig, gender,
+                mAtm.mGrammaticalManagerInternal, mUid);
+    }
+
+    static boolean applyConfigGenderOverride(@NonNull Configuration overrideConfig,
+            @Configuration.GrammaticalGender int override,
+            GrammaticalInflectionManagerInternal service, int uid) {
+        final boolean canGetSystemValue = service != null
+                && service.canGetSystemGrammaticalGender(uid);
+
+        // The priority here is as follows:
+        // - app-specific override if set
+        // - system value if allowed to see it
+        // - global configuration otherwise
+        final int targetValue = (override != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED)
+                ? override
+                : canGetSystemValue
+                        ? Configuration.GRAMMATICAL_GENDER_UNDEFINED
+                        : service != null
+                                ? service.getGrammaticalGenderFromDeveloperSettings()
+                                : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        if (overrideConfig.getGrammaticalGenderRaw() == targetValue) {
+            return false;
+        }
+        overrideConfig.setGrammaticalGender(targetValue);
+        return true;
+    }
 }
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 4a8d73d2..07cda37 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -875,7 +875,7 @@
         }
 
         private void registerBroadcastReceivers() {
-            PackageMonitor monitor = new PackageMonitor() {
+            PackageMonitor monitor = new PackageMonitor(true) {
                 /**
                  * Checks if the package contains a print service.
                  *
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 64253e1..3628a57 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -1030,7 +1030,7 @@
     }
 
     @Test
-    public void testBrightnessBasedOnLastObservedLux() throws Exception {
+    public void testBrightnessBasedOnLastUsedLux() throws Exception {
         ArgumentCaptor<SensorEventListener> listenerCaptor =
                 ArgumentCaptor.forClass(SensorEventListener.class);
         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
@@ -1054,7 +1054,7 @@
                 /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
                 /* shouldResetShortTermModel= */ true);
         assertEquals(normalizedBrightness,
-                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                         /* brightnessEvent= */ null), EPSILON);
     }
 
@@ -1090,7 +1090,7 @@
                 mController.getAutomaticScreenBrightness(
                         /* brightnessEvent= */ null), EPSILON);
         assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
-                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                         /* brightnessEvent= */ null), EPSILON);
     }
 
@@ -1128,7 +1128,7 @@
         assertEquals(normalizedBrightness,
                 mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
         assertEquals(normalizedBrightness,
-                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                         /* brightnessEvent= */ null), EPSILON);
     }
 
@@ -1163,7 +1163,7 @@
         assertEquals(normalizedBrightness,
                 mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON);
         assertEquals(normalizedBrightness,
-                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                mController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                         /* brightnessEvent= */ null), EPSILON);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 78ebdb1..80f38eb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1776,7 +1776,7 @@
         float brightness = 0.277f;
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
         when(mHolder.automaticBrightnessController
-                .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class)))
+                .getAutomaticScreenBrightnessBasedOnLastUsedLux(any(BrightnessEvent.class)))
                 .thenReturn(brightness);
         when(mHolder.hbmController.getCurrentBrightnessMax())
                 .thenReturn(PowerManager.BRIGHTNESS_MAX);
@@ -1840,7 +1840,7 @@
         float brightness = 0.277f;
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
         when(mHolder.automaticBrightnessController
-                .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class)))
+                .getAutomaticScreenBrightnessBasedOnLastUsedLux(any(BrightnessEvent.class)))
                 .thenReturn(brightness);
         when(mHolder.hbmController.getCurrentBrightnessMax())
                 .thenReturn(PowerManager.BRIGHTNESS_MAX);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 13a1445..c5105e7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -32,7 +32,9 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.SensorManager;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.PowerManager;
 import android.view.Display;
@@ -41,7 +43,11 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.BrightnessMappingStrategy;
 import com.android.server.display.BrightnessSetting;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
 import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
@@ -75,14 +81,14 @@
     @Mock
     private HandlerExecutor mBrightnessChangeExecutor;
 
-    private final DisplayBrightnessController.Injector mInjector = new
-            DisplayBrightnessController.Injector() {
-        @Override
-        DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
-                Context context, int displayId, DisplayManagerFlags flags) {
-            return mDisplayBrightnessStrategySelector;
-        }
-    };
+    private final DisplayBrightnessController.Injector mInjector =
+            new DisplayBrightnessController.Injector() {
+                @Override
+                DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
+                        Context context, int displayId, DisplayManagerFlags flags) {
+                    return mDisplayBrightnessStrategySelector;
+                }
+            };
 
     private DisplayBrightnessController mDisplayBrightnessController;
 
@@ -196,10 +202,9 @@
                 pendingScreenBrightness, /* delta= */ 0.0f);
         assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
                 pendingScreenBrightness, /* delta= */ 0.0f);
-        verify(mBrightnessChangeExecutor, times(2))
-                .execute(mOnBrightnessChangeRunnable);
-        verify(temporaryBrightnessStrategy, times(2))
-                .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        verify(mBrightnessChangeExecutor, times(2)).execute(mOnBrightnessChangeRunnable);
+        verify(temporaryBrightnessStrategy, times(2)).setTemporaryScreenBrightness(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
         assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
                 PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
     }
@@ -253,8 +258,7 @@
         // Sets the appropriate value when valid, and not equal to the current brightness
         float brightnessValue = 0.3f;
         mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue,
-                0.0f);
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue, 0.0f);
         verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
         verify(mBrightnessSetting).setBrightness(brightnessValue);
 
@@ -265,8 +269,7 @@
         // Does nothing if the value is same as the current brightness
         brightnessValue = 0.2f;
         mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue);
-        verify(mBrightnessChangeExecutor, times(2))
-                .execute(mOnBrightnessChangeRunnable);
+        verify(mBrightnessChangeExecutor, times(2)).execute(mOnBrightnessChangeRunnable);
         mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
         verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
     }
@@ -283,14 +286,22 @@
         assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness),
                 /* delta= */ 0);
 
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+
+        AutomaticBrightnessStrategy automaticBrightnessStrategy =
+                mock(AutomaticBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy())
+                .thenReturn(automaticBrightnessStrategy);
+
         when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(automaticBrightnessController.convertToAdjustedNits(brightness))
-                .thenReturn(adjustedNits);
+        when(automaticBrightnessController.convertToAdjustedNits(brightness)).thenReturn(
+                adjustedNits);
         mDisplayBrightnessController.setAutomaticBrightnessController(
                 automaticBrightnessController);
 
+        verify(automaticBrightnessStrategy)
+                .setAutomaticBrightnessController(automaticBrightnessController);
         assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
         assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness),
                 /* delta= */ 0);
@@ -305,12 +316,17 @@
         assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
                 mDisplayBrightnessController.getBrightnessFromNits(nits), /* delta= */ 0);
 
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        AutomaticBrightnessStrategy automaticBrightnessStrategy =
+                mock(AutomaticBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy())
+                .thenReturn(automaticBrightnessStrategy);
         when(automaticBrightnessController.getBrightnessFromNits(nits)).thenReturn(brightness);
         mDisplayBrightnessController.setAutomaticBrightnessController(
                 automaticBrightnessController);
-
+        verify(automaticBrightnessStrategy)
+                .setAutomaticBrightnessController(automaticBrightnessController);
         assertEquals(brightness, mDisplayBrightnessController.getBrightnessFromNits(nits),
                 /* delta= */ 0);
     }
@@ -331,8 +347,12 @@
         // value
         float nits = 200f;
         float brightness = 0.3f;
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        AutomaticBrightnessStrategy automaticBrightnessStrategy =
+                mock(AutomaticBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy())
+                .thenReturn(automaticBrightnessStrategy);
         when(automaticBrightnessController.getBrightnessFromNits(nits)).thenReturn(brightness);
         when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
         mDisplayBrightnessController.setAutomaticBrightnessController(
@@ -375,12 +395,18 @@
         float nits1 = 200f;
         float brightnessValue2 = 0.5f;
         float nits2 = 300f;
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
         when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1);
         when(automaticBrightnessController.convertToNits(brightnessValue2)).thenReturn(nits2);
+        AutomaticBrightnessStrategy automaticBrightnessStrategy =
+                mock(AutomaticBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy())
+                .thenReturn(automaticBrightnessStrategy);
         mDisplayBrightnessController.setAutomaticBrightnessController(
                 automaticBrightnessController);
+        verify(automaticBrightnessStrategy)
+                .setAutomaticBrightnessController(automaticBrightnessController);
 
         mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */);
         verify(mBrightnessSetting).setUserSerial(1);
@@ -399,8 +425,8 @@
         OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
         when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
                 offloadBrightnessStrategy);
-        boolean brightnessUpdated =
-                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        boolean brightnessUpdated = mDisplayBrightnessController.setBrightnessFromOffload(
+                brightness);
         verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
         assertTrue(brightnessUpdated);
     }
@@ -409,8 +435,8 @@
     public void setBrightnessFromOffload_OffloadStrategyNull() {
         float brightness = 0.4f;
         when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null);
-        boolean brightnessUpdated =
-                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        boolean brightnessUpdated = mDisplayBrightnessController.setBrightnessFromOffload(
+                brightness);
         assertFalse(brightnessUpdated);
     }
 
@@ -421,9 +447,42 @@
         when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness);
         when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
                 offloadBrightnessStrategy);
-        boolean brightnessUpdated =
-                mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+        boolean brightnessUpdated = mDisplayBrightnessController.setBrightnessFromOffload(
+                brightness);
         verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness);
         assertFalse(brightnessUpdated);
     }
+
+    @Test
+    public void setupAutoBrightness_setsAutomaticStrategyAndAutoBrightnessFallbackStrategy() {
+        // Setup the strategy mocks
+        AutomaticBrightnessStrategy automaticBrightnessStrategy = mock(
+                AutomaticBrightnessStrategy.class);
+        AutoBrightnessFallbackStrategy autoBrightnessFallbackStrategy = mock(
+                AutoBrightnessFallbackStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy())
+                .thenReturn(automaticBrightnessStrategy);
+        when(mDisplayBrightnessStrategySelector.getAutoBrightnessFallbackStrategy())
+                .thenReturn(autoBrightnessFallbackStrategy);
+
+        // Setup the argument mocks
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        SensorManager sensorManager = mock(SensorManager.class);
+        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+        Handler handler = mock(Handler.class);
+        BrightnessMappingStrategy brightnessMappingStrategy = mock(BrightnessMappingStrategy.class);
+        boolean isEnabled = true;
+        int leadDisplayId = 2;
+
+        mDisplayBrightnessController.setUpAutoBrightness(automaticBrightnessController,
+                sensorManager, displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled,
+                leadDisplayId);
+        assertEquals(automaticBrightnessController,
+                mDisplayBrightnessController.mAutomaticBrightnessController);
+        verify(automaticBrightnessStrategy).setAutomaticBrightnessController(
+                automaticBrightnessController);
+        verify(autoBrightnessFallbackStrategy).setupAutoBrightnessFallbackSensor(sensorManager,
+                displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled, leadDisplayId);
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index f270650..ae6361b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -40,9 +40,11 @@
 import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
 import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
 import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
@@ -86,6 +88,8 @@
     @Mock
     private OffloadBrightnessStrategy mOffloadBrightnessStrategy;
     @Mock
+    private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy;
+    @Mock
     private Resources mResources;
     @Mock
     private DisplayManagerFlags mDisplayManagerFlags;
@@ -146,6 +150,11 @@
                         DisplayManagerFlags displayManagerFlags) {
                     return mOffloadBrightnessStrategy;
                 }
+
+                @Override
+                AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
+                    return mAutoBrightnessFallbackStrategy;
+                }
             };
 
     @Rule
@@ -187,8 +196,8 @@
         when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
                 DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
         assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(
-                new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
-                        0.1f, false)),
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE,
+                                0.1f, false)),
                 mDozeBrightnessModeStrategy);
     }
 
@@ -309,6 +318,26 @@
     }
 
     @Test
+    public void selectStrategy_selectsAutomaticFallbackStrategyWhenValid() {
+        when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
+        when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false);
+        when(mAutoBrightnessFallbackStrategy.isValid()).thenReturn(true);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
+                mAutoBrightnessFallbackStrategy);
+    }
+
+    @Test
     public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() {
         when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false);
         mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
@@ -341,23 +370,15 @@
         StrategySelectionNotifyRequest strategySelectionNotifyRequest =
                 new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON,
                         mFollowerBrightnessStrategy, 0.1f,
-                        false, false);
-        verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
-        verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
-        verify(mDozeBrightnessModeStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
-        verify(mFollowerBrightnessStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
-        verify(mBoostBrightnessStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
-        verify(mOverrideBrightnessStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
-        verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
-        verify(mAutomaticBrightnessStrategy).strategySelectionPostProcessor(
-                eq(strategySelectionNotifyRequest));
+                        false, false, false);
+
+        for (DisplayBrightnessStrategy displayBrightnessStrategy :
+                mDisplayBrightnessStrategySelector.mDisplayBrightnessStrategies) {
+            if (displayBrightnessStrategy != null) {
+                verify(displayBrightnessStrategy).strategySelectionPostProcessor(
+                        eq(strategySelectionNotifyRequest));
+            }
+        }
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
new file mode 100644
index 0000000..7a6a911
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.BrightnessMappingStrategy;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.ScreenOffBrightnessSensorController;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategyExecutionRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutoBrightnessFallbackStrategyTest {
+    private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy;
+
+    @Mock
+    private Sensor mScreenOffBrightnessSensor;
+
+    @Mock
+    private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mAutoBrightnessFallbackStrategy = new AutoBrightnessFallbackStrategy(
+                new AutoBrightnessFallbackStrategy.Injector() {
+                    @Override
+                    public Sensor getScreenOffBrightnessSensor(SensorManager sensorManager,
+                            DisplayDeviceConfig displayDeviceConfig) {
+                        return mScreenOffBrightnessSensor;
+                    }
+
+                    @Override
+                    public ScreenOffBrightnessSensorController
+                            getScreenOffBrightnessSensorController(SensorManager sensorManager,
+                                    Sensor lightSensor, Handler handler,
+                                    ScreenOffBrightnessSensorController.Clock clock,
+                                    int[] sensorValueToLux,
+                                    BrightnessMappingStrategy brightnessMapper) {
+                        return mScreenOffBrightnessSensorController;
+                    }
+                });
+    }
+
+    @Test
+    public void testUpdateBrightnessWhenScreenDozeStateIsRequested() {
+        // Setup the argument mocks
+        SensorManager sensorManager = mock(SensorManager.class);
+        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+        Handler handler = mock(Handler.class);
+        BrightnessMappingStrategy brightnessMappingStrategy = mock(BrightnessMappingStrategy.class);
+        boolean isEnabled = true;
+        int leadDisplayId = 2;
+
+        int[] sensorValueToLux = new int[]{50, 100};
+        when(displayDeviceConfig.getScreenOffBrightnessSensorValueToLux()).thenReturn(
+                sensorValueToLux);
+
+        mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(sensorManager,
+                displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled, leadDisplayId);
+
+        assertEquals(mScreenOffBrightnessSensor,
+                mAutoBrightnessFallbackStrategy.mScreenOffBrightnessSensor);
+        assertEquals(mScreenOffBrightnessSensorController,
+                mAutoBrightnessFallbackStrategy.getScreenOffBrightnessSensorController());
+
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        float fallbackBrightness = 0.2f;
+        when(mScreenOffBrightnessSensorController.getAutomaticScreenBrightness()).thenReturn(
+                fallbackBrightness);
+
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(fallbackBrightness)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(fallbackBrightness)
+                        .setDisplayBrightnessStrategyName(mAutoBrightnessFallbackStrategy.getName())
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mAutoBrightnessFallbackStrategy.updateBrightness(
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+    }
+
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
index fd43720..09f5bb6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java
@@ -343,7 +343,7 @@
                 AutomaticBrightnessController.class);
         when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
                 .thenReturn(automaticScreenBrightness);
-        when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+        when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                 any(BrightnessEvent.class)))
                 .thenReturn(automaticScreenBrightness);
         mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
@@ -352,7 +352,7 @@
                 mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
                         new BrightnessEvent(DISPLAY_ID)), 0.0f);
         assertEquals(automaticScreenBrightness,
-                mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                         new BrightnessEvent(DISPLAY_ID)), 0.0f);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 54f2268..3e78118 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -341,7 +341,7 @@
                 AutomaticBrightnessController.class);
         when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
                 .thenReturn(automaticScreenBrightness);
-        when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+        when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                 any(BrightnessEvent.class)))
                 .thenReturn(automaticScreenBrightness);
         mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
@@ -350,7 +350,7 @@
                 mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
                         new BrightnessEvent(DISPLAY_ID)), 0.0f);
         assertEquals(automaticScreenBrightness,
-                mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastUsedLux(
                         new BrightnessEvent(DISPLAY_ID)), 0.0f);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/autofill/OWNERS b/services/tests/servicestests/src/com/android/server/autofill/OWNERS
new file mode 100644
index 0000000..70106d1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/autofill/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
index af6f6f2..608b306 100644
--- a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
+++ b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
@@ -112,7 +112,7 @@
     public void testSystemBackupPayload_returnsGender()
             throws IOException, ClassNotFoundException {
         doReturn(Configuration.GRAMMATICAL_GENDER_MASCULINE).when(mGrammaticalInflectionService)
-                .getSystemGrammaticalGender(any(), eq(DEFAULT_USER_ID));
+                .getSystemGrammaticalGender(eq(DEFAULT_USER_ID));
 
         int gender = convertByteArrayToInt(mBackupHelper.getSystemBackupPayload(DEFAULT_USER_ID));
 
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 902ffed..4faeea5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -937,6 +937,94 @@
     }
 
     @Test
+    public void onHotplug_doNotSend_systemAudioModeRequestWithParameter(){
+        // Add a device to the network and assert that this device is included in the list of
+        // devices.
+        HdmiDeviceInfo infoAudioSystem = HdmiDeviceInfo.cecDeviceBuilder()
+            .setLogicalAddress(ADDR_AUDIO_SYSTEM)
+            .setPhysicalAddress(0x2000)
+            .setPortId(2)
+            .setDeviceType(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+            .setVendorId(0x1000)
+            .setDisplayName("Audio System")
+            .build();
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoAudioSystem);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+            .hasSize(1);
+        mDeviceEventListeners.clear();
+        assertThat(mDeviceEventListeners.size()).isEqualTo(0);
+
+        // Connect port 2 (ARC port)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+
+        // AVR connection
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+            ADDR_AUDIO_SYSTEM,
+            ADDR_TV);
+
+        mNativeWrapper.onCecMessage(initiateArc);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+            ADDR_TV,
+            ADDR_AUDIO_SYSTEM);
+        // <Report ARC Initiated> should only be sent after SAD querying is done
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+        // Finish querying SADs
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+        mNativeWrapper.clearResultMessages();
+
+        // Audio System still acking polls. Allowing detection by HotplugDetectionAction
+        mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.SUCCESS);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Hotplug event
+        mHdmiCecLocalDeviceTv.onHotplug(2, true);
+
+        // Audio System replies to <Give System Audio Mode> with <System Audio Mode Status>[On]
+        HdmiCecMessage reportSystemAudioModeOn =
+            HdmiCecMessageBuilder.buildReportSystemAudioMode(
+                ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                true);
+        mHdmiControlService.handleCecCommand(reportSystemAudioModeOn);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Hotplug event when turn off the audio system
+        mHdmiCecLocalDeviceTv.onHotplug(2, false);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Some audio systems (eg. Sony) might trigger 5V status from false to true when the
+        // devices are off
+        mHdmiCecLocalDeviceTv.onHotplug(2, true);
+
+        // Audio System replies to <Give System Audio Mode> with <System Audio Mode Status>
+        HdmiCecMessage reportSystemAudioMode =
+            HdmiCecMessageBuilder.buildReportSystemAudioMode(
+                ADDR_AUDIO_SYSTEM,
+                mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                true);
+        mHdmiControlService.handleCecCommand(reportSystemAudioMode);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage systemAudioModeRequest = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+            mTvLogicalAddress, ADDR_AUDIO_SYSTEM, mTvPhysicalAddress, true);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest);
+    }
+
+    @Test
     public void listenerInvokedIfPhysicalAddressReported() {
         mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
         assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 1d3dacc..9a92c70 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -408,6 +408,60 @@
     }
 
     @Test
+    public void setRcProfileRootMenu_reportFeatureBroadcast() {
+        setRcProfileSourceDeviceTestHelper(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
+                HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+    }
+
+    @Test
+    public void setRcProfileSetupMenu_reportFeatureBroadcast() {
+        setRcProfileSourceDeviceTestHelper(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
+                HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+    }
+
+    @Test
+    public void setRcProfileContentMenu_reportFeatureBroadcast() {
+        setRcProfileSourceDeviceTestHelper(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
+                HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+    }
+
+    @Test
+    public void setRcProfileTopMenu_reportFeatureBroadcast() {
+        setRcProfileSourceDeviceTestHelper(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
+                HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+    }
+
+    @Test
+    public void setRcProfileMediaSensitiveMenu_reportFeatureBroadcast() {
+        setRcProfileSourceDeviceTestHelper(
+                HdmiControlManager
+                        .CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
+                HdmiControlManager.RC_PROFILE_SOURCE_MENU_HANDLED);
+    }
+
+    /** Helper method to test if feature discovery message sent given RCProfile change */
+    private void setRcProfileSourceDeviceTestHelper(final String setting, final int val) {
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(setting, val);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(Constants.ADDR_PLAYBACK_1,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0,
+                Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM),
+                mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(),
+                mPlaybackDeviceSpy.getDeviceFeatures());
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
+    }
+
+    @Test
     public void disableAndReenableCec_volumeControlReturnsToOriginalValue_enabled() {
         int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED;
         mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
index 920c376..eed9975 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
@@ -16,11 +16,14 @@
 
 package com.android.server.hdmi;
 
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
+
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.os.Looper;
 import android.os.test.TestLooper;
@@ -35,6 +38,7 @@
 import org.junit.runners.JUnit4;
 
 import java.util.Collections;
+import java.util.List;
 
 /**
  * TV specific tests for {@link HdmiControlService} class.
@@ -47,6 +51,7 @@
     private static final String TAG = "HdmiControlServiceTvTest";
     private HdmiControlService mHdmiControlService;
     private HdmiCecController mHdmiCecController;
+    private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
     private FakeNativeWrapper mNativeWrapper;
     private HdmiEarcController mHdmiEarcController;
     private FakeEarcNativeWrapper mEarcNativeWrapper;
@@ -90,6 +95,8 @@
         mHdmiControlService.initService();
 
         mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
     }
 
     @Test
@@ -139,4 +146,23 @@
         assertThat(mHdmiControlService
                 .verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10"))).isFalse();
     }
+
+    @Test
+    public void setRcProfileTv_reportFeatureBroadcast() {
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
+                HdmiControlManager.RC_PROFILE_TV_NONE);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(Constants.ADDR_TV,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0, List.of(DEVICE_TV),
+                mHdmiCecLocalDeviceTv.getRcProfile(), mHdmiCecLocalDeviceTv.getRcFeatures(),
+                mHdmiCecLocalDeviceTv.getDeviceFeatures());
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 1da5001..b0a3374 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -525,10 +525,13 @@
 
         // The activity shouldn't start relaunching since it doesn't have any desk resources.
         assertFalse(activity.isRelaunching());
+        // The activity configuration ui mode should match.
+        final var activityConfig = activity.getConfiguration();
+        assertEquals(newConfig.uiMode, activityConfig.uiMode);
 
         // The configuration change is still sent to the activity, even if it doesn't relaunch.
         final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(activity.token, newConfig,
+                ActivityConfigurationChangeItem.obtain(activity.token, activityConfig,
                         activity.getActivityWindowInfo());
         verify(mClientLifecycleManager).scheduleTransactionItem(
                 eq(activity.app.getThread()), eq(expected));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 87f26e5..c45c86c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.DisplayManagerGlobal;
@@ -86,6 +87,7 @@
 import com.android.server.display.DisplayControl;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.firewall.IntentFirewall;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
 import com.android.server.input.InputManagerService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
@@ -208,6 +210,11 @@
         setUpLocalServices();
         setUpActivityTaskManagerService();
         setUpWindowManagerService();
+
+        // We never load the system settings in the tests, thus need to setup the grammatical
+        // gender configuration explicitly.
+        mAtmService.getGlobalConfiguration().setGrammaticalGender(
+                Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
     }
 
     private void setUpSystemCore() {
@@ -337,6 +344,18 @@
         };
         when(umi.isUserVisible(anyInt())).thenAnswer(isUserVisibleAnswer);
         when(umi.isUserVisible(anyInt(), anyInt())).thenAnswer(isUserVisibleAnswer);
+
+        final var gimi = mock(
+                GrammaticalInflectionManagerInternal.class, withSettings().stubOnly());
+        doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+                gimi).getGrammaticalGenderFromDeveloperSettings();
+        doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+                gimi).getSystemGrammaticalGender(anyInt());
+        doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+                gimi).mergedFinalSystemGrammaticalGender();
+        doReturn(false).when(gimi).canGetSystemGrammaticalGender(anyInt());
+        doReturn(gimi).when(
+                () -> LocalServices.getService(GrammaticalInflectionManagerInternal.class));
     }
 
     private void setUpActivityTaskManagerService() {
@@ -475,6 +494,7 @@
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         LocalServices.removeServiceForTest(ImeTargetVisibilityPolicy.class);
+        LocalServices.removeServiceForTest(GrammaticalInflectionManagerInternal.class);
     }
 
     Description getDescription() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e5d7b40e..1d01420 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2538,7 +2538,8 @@
             }
         }
 
-        PackageMonitor mPackageMonitor = new PackageMonitor() {
+        PackageMonitor mPackageMonitor = new PackageMonitor(
+                /* supportsPackageRestartQuery= */ true) {
 
             @Override
             public boolean onHandleForceStop(Intent intent, String[] packages, int uid,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7db4180..4c719dd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -6007,7 +6007,7 @@
             defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false);
-            defaults.putInt(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
+            defaults.putLong(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
             defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false);
             defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
             defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false);
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index b568f07..e6515f13 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -65,6 +65,7 @@
 public final class PreciseDataConnectionState implements Parcelable {
     private final @TransportType int mTransportType;
     private final int mId;
+    private final int mNetId;
     private final @DataState int mState;
     private final @NetworkType int mNetworkType;
     private final @DataFailureCause int mFailCause;
@@ -134,7 +135,7 @@
                                       @ApnType int apnTypes, @NonNull String apn,
                                       @Nullable LinkProperties linkProperties,
                                       @DataFailureCause int failCause) {
-        this(AccessNetworkConstants.TRANSPORT_TYPE_INVALID, -1, state, networkType,
+        this(AccessNetworkConstants.TRANSPORT_TYPE_INVALID, -1, -1, state, networkType,
                 linkProperties, failCause, new ApnSetting.Builder()
                         .setApnTypeBitmask(apnTypes)
                         .setApnName(apn)
@@ -158,13 +159,14 @@
      * @param defaultQos If there is a valid QoS for the default bearer supporting this data call,
      *        (supported for LTE and NR), then this is specified. Otherwise it should be null.
      */
-    private PreciseDataConnectionState(@TransportType int transportType, int id,
+    private PreciseDataConnectionState(@TransportType int transportType, int id, int netId,
             @DataState int state, @NetworkType int networkType,
             @Nullable LinkProperties linkProperties, @DataFailureCause int failCause,
             @Nullable ApnSetting apnSetting, @Nullable Qos defaultQos,
             @NetworkValidationStatus int networkValidationStatus) {
         mTransportType = transportType;
         mId = id;
+        mNetId = netId;
         mState = state;
         mNetworkType = networkType;
         mLinkProperties = linkProperties;
@@ -182,6 +184,7 @@
     private PreciseDataConnectionState(Parcel in) {
         mTransportType = in.readInt();
         mId = in.readInt();
+        mNetId = in.readInt();
         mState = in.readInt();
         mNetworkType = in.readInt();
         mLinkProperties = in.readParcelable(
@@ -244,6 +247,14 @@
     }
 
     /**
+     * @return the current TelephonyNetworkAgent ID. {@code -1} if no network agent.
+     * @hide
+     */
+    public int getNetId() {
+        return mNetId;
+    }
+
+    /**
      * @return The high-level state of this data connection.
      */
     public @DataState int getState() {
@@ -363,6 +374,7 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeInt(mTransportType);
         out.writeInt(mId);
+        out.writeInt(mNetId);
         out.writeInt(mState);
         out.writeInt(mNetworkType);
         out.writeParcelable(mLinkProperties, flags);
@@ -386,7 +398,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mTransportType, mId, mState, mNetworkType, mFailCause,
+        return Objects.hash(mTransportType, mId, mNetId, mState, mNetworkType, mFailCause,
                 mLinkProperties, mApnSetting, mDefaultQos, mNetworkValidationStatus);
     }
 
@@ -398,6 +410,7 @@
         PreciseDataConnectionState that = (PreciseDataConnectionState) o;
         return mTransportType == that.mTransportType
                 && mId == that.mId
+                && mNetId == that.mNetId
                 && mState == that.mState
                 && mNetworkType == that.mNetworkType
                 && mFailCause == that.mFailCause
@@ -412,17 +425,18 @@
     public String toString() {
         StringBuilder sb = new StringBuilder();
 
-        sb.append(" state: " + TelephonyUtils.dataStateToString(mState));
-        sb.append(", transport: "
-                + AccessNetworkConstants.transportTypeToString(mTransportType));
-        sb.append(", id: " + mId);
-        sb.append(", network type: " + TelephonyManager.getNetworkTypeName(mNetworkType));
-        sb.append(", APN Setting: " + mApnSetting);
-        sb.append(", link properties: " + mLinkProperties);
-        sb.append(", default QoS: " + mDefaultQos);
-        sb.append(", fail cause: " + DataFailCause.toString(mFailCause));
-        sb.append(", network validation status: "
-                + networkValidationStatusToString(mNetworkValidationStatus));
+        sb.append(" state: ").append(TelephonyUtils.dataStateToString(mState));
+        sb.append(", transport: ").append(
+                AccessNetworkConstants.transportTypeToString(mTransportType));
+        sb.append(", id: ").append(mId);
+        sb.append(", netId: ").append(mNetId);
+        sb.append(", network type: ").append(TelephonyManager.getNetworkTypeName(mNetworkType));
+        sb.append(", APN Setting: ").append(mApnSetting);
+        sb.append(", link properties: ").append(mLinkProperties);
+        sb.append(", default QoS: ").append(mDefaultQos);
+        sb.append(", fail cause: ").append(DataFailCause.toString(mFailCause));
+        sb.append(", network validation status: ").append(
+                networkValidationStatusToString(mNetworkValidationStatus));
 
         return sb.toString();
     }
@@ -463,6 +477,11 @@
          */
         private int mId = -1;
 
+        /**
+         * The current TelephonyNetworkAgent ID. {@code -1} if no network agent.
+         */
+        private int mNetworkAgentId = -1;
+
         /** The state of the data connection */
         private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
 
@@ -511,6 +530,17 @@
         }
 
         /**
+         * Set the id of the data connection.
+         *
+         * @param agentId The id of the data connection
+         * @return The builder
+         */
+        public @NonNull Builder setNetworkAgentId(int agentId) {
+            mNetworkAgentId = agentId;
+            return this;
+        }
+
+        /**
          * Set the state of the data connection.
          *
          * @param state The state of the data connection
@@ -598,8 +628,8 @@
          * @return The {@link PreciseDataConnectionState} instance
          */
         public PreciseDataConnectionState build() {
-            return new PreciseDataConnectionState(mTransportType, mId, mState, mNetworkType,
-                    mLinkProperties, mFailCause, mApnSetting, mDefaultQos,
+            return new PreciseDataConnectionState(mTransportType, mId, mNetworkAgentId, mState,
+                    mNetworkType, mLinkProperties, mFailCause, mApnSetting, mDefaultQos,
                     mNetworkValidationStatus);
         }
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f076de3..25e2d82 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -19266,6 +19266,26 @@
     }
 
     /**
+     * Returns whether the AOSP domain selection service is supported.
+     *
+     * @return {@code true} if the AOSP domain selection service is supported.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+    public boolean isAospDomainSelectionService() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.isAospDomainSelectionService();
+            }
+        } catch (RemoteException ex) {
+            Rlog.w(TAG, "RemoteException", ex);
+        }
+        return false;
+    }
+
+    /**
      * Returns the primary IMEI (International Mobile Equipment Identity) of the device as
      * mentioned in GSMA TS.37. {@link #getImei(int)} returns the IMEI that belongs to the selected
      * slotID whereas this API {@link #getPrimaryImei()} returns primary IMEI of the device.
diff --git a/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
new file mode 100644
index 0000000..a7eda48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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.telephony.satellite;
+
+/**
+ * Interface for satellite communication allowed state callback.
+ * @hide
+ */
+oneway interface ISatelliteCommunicationAllowedStateCallback {
+    /**
+     * Telephony does not guarantee that whenever there is a change in communication allowed
+     * state, this API will be called. Telephony does its best to detect the changes and notify
+     * its listners accordingly.
+     *
+     * @param allowed whether satellite communication state or not
+     */
+    void onSatelliteCommunicationAllowedStateChanged(in boolean isAllowed);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
new file mode 100644
index 0000000..1a87020
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+
+/**
+ * A callback class for monitoring satellite communication allowed state changed events.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteCommunicationAllowedStateCallback {
+
+    /**
+     * Telephony does not guarantee that whenever there is a change in communication allowed state,
+     * this API will be called. Telephony does its best to detect the changes and notify its
+     * listeners accordingly.
+     *
+     * @param isAllowed {@code true} means satellite allow state is changed,
+     *                  {@code false} satellite allow state is not changed
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onSatelliteCommunicationAllowedStateChanged(boolean isAllowed);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 40ad312..0bb5fd5 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -91,6 +91,11 @@
             ISatelliteSupportedStateCallback> sSatelliteSupportedStateCallbackMap =
             new ConcurrentHashMap<>();
 
+    private static final ConcurrentHashMap<SatelliteCommunicationAllowedStateCallback,
+            ISatelliteCommunicationAllowedStateCallback>
+            sSatelliteCommunicationAllowedStateCallbackMap =
+            new ConcurrentHashMap<>();
+
     private final int mSubId;
 
     /**
@@ -2393,7 +2398,89 @@
         }
     }
 
-    @Nullable private static ITelephony getITelephony() {
+    /**
+     * Registers for the satellite communication allowed state changed.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle satellite communication allowed state changed event.
+     * @return The {@link SatelliteResult} result of the operation.
+     * @throws SecurityException     if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteResult
+    public int registerForCommunicationAllowedStateChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteCommunicationAllowedStateCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ISatelliteCommunicationAllowedStateCallback internalCallback =
+                        new ISatelliteCommunicationAllowedStateCallback.Stub() {
+                            @Override
+                            public void onSatelliteCommunicationAllowedStateChanged(
+                                    boolean isAllowed) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteCommunicationAllowedStateChanged(
+                                                isAllowed)));
+                            }
+                        };
+                sSatelliteCommunicationAllowedStateCallbackMap.put(callback, internalCallback);
+                return telephony.registerForCommunicationAllowedStateChanged(
+                        mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForCommunicationAllowedStateChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+        return SATELLITE_RESULT_REQUEST_FAILED;
+    }
+
+    /**
+     * Unregisters for the satellite communication allowed state changed.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to
+     *                 {@link #registerForCommunicationAllowedStateChanged(Executor,
+     *                 SatelliteCommunicationAllowedStateCallback)}
+     * @throws SecurityException     if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void unregisterForCommunicationAllowedStateChanged(
+            @NonNull SatelliteCommunicationAllowedStateCallback callback) {
+        Objects.requireNonNull(callback);
+        ISatelliteCommunicationAllowedStateCallback internalCallback =
+                sSatelliteCommunicationAllowedStateCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForCommunicationAllowedStateChanged(mSubId,
+                            internalCallback);
+                } else {
+                    loge("unregisterForCommunicationAllowedStateChanged: No internal callback.");
+                }
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForCommunicationAllowedStateChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    @Nullable
+    private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
                 .getTelephonyServiceRegisterer()
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f25fc36..d4da736 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -69,6 +69,7 @@
 import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
+import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
@@ -3244,6 +3245,12 @@
     boolean clearDomainSelectionServiceOverride();
 
     /**
+     * @return {@code true} if the AOSP domain selection service is supported,
+     *         {@code false} otherwise.
+     */
+    boolean isAospDomainSelectionService();
+
+    /**
      * Enable or disable notifications sent for cellular identifier disclosure events.
      *
      * Disclosure events are defined as instances where a device has sent a cellular identifier
@@ -3341,4 +3348,29 @@
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void unregisterForSatelliteSupportedStateChanged(int subId,
             in ISatelliteSupportedStateCallback callback);
+
+    /**
+     * Registers for satellite communication allowed state changed.
+     *
+     * @param subId The subId of the subscription to register for communication allowed state.
+     * @param callback The callback to handle the communication allowed state changed event.
+     *
+     * @return The {@link SatelliteError} result of the operation.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int registerForCommunicationAllowedStateChanged(int subId,
+            in ISatelliteCommunicationAllowedStateCallback callback);
+
+    /**
+     * Unregisters for satellite communication allowed state.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for supported state changed.
+     * @param callback The callback that was passed to registerForCommunicationAllowedStateChanged.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForCommunicationAllowedStateChanged(int subId,
+            in ISatelliteCommunicationAllowedStateCallback callback);
 }
diff --git a/tests/FlickerTests/IME/OWNERS b/tests/FlickerTests/IME/OWNERS
index 301fafa..ae1098d 100644
--- a/tests/FlickerTests/IME/OWNERS
+++ b/tests/FlickerTests/IME/OWNERS
@@ -1,2 +1,3 @@
 # ime
 # Bug component: 34867
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
index 31506b5..d22bdcf 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
@@ -24,6 +24,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.traces.component.ComponentNameMatcher
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import org.junit.FixMethodOrder
@@ -90,6 +91,11 @@
     @Ignore("Not applicable to this CUJ. Display turns off during transition")
     override fun taskBarWindowIsAlwaysVisible() {}
 
+    @FlakyTest(bugId = 338178020)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 23fa91c..2df3da6 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.testapp;
 
-import androidx.annotation.NonNull;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
@@ -103,18 +102,13 @@
     }
 
     private static SplitPairRule createSplitPairRules(@NonNull String layoutDirection) {
-        final Set<SplitPairFilter> pairFilters = new HashSet<>();
-        final SplitPairFilter activitiesPair = new SplitPairFilter(
-                ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
-                ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT,
-                null /* secondaryActivityIntentAction */);
-        pairFilters.add(activitiesPair);
+        final Set<SplitPairFilter> pairFilters = getSplitPairFilters();
         final SplitAttributes splitAttributes = new SplitAttributes.Builder()
                 .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
                 .setLayoutDirection(parseLayoutDirection(layoutDirection))
                 .build();
         // Setting thresholds to ALWAYS_ALLOW values to make it easy for running on all devices.
-        final SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+        return new SplitPairRule.Builder(pairFilters)
                 .setDefaultSplitAttributes(splitAttributes)
                 .setMinWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
                 .setMinHeightDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
@@ -122,7 +116,22 @@
                 .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
                 .setMaxAspectRatioInLandscape(EmbeddingAspectRatio.ALWAYS_ALLOW)
                 .build();
-        return rule;
+    }
+
+    @NonNull
+    private static Set<SplitPairFilter> getSplitPairFilters() {
+        final Set<SplitPairFilter> pairFilters = new HashSet<>();
+        final SplitPairFilter mainAndSecondaryActivitiesPair = new SplitPairFilter(
+                ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
+                ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT,
+                null /* secondaryActivityIntentAction */);
+        pairFilters.add(mainAndSecondaryActivitiesPair);
+        final SplitPairFilter mainAndTrampolineActivitiesPair = new SplitPairFilter(
+                ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
+                ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT,
+                null /* secondaryActivityIntentAction */);
+        pairFilters.add(mainAndTrampolineActivitiesPair);
+        return pairFilters;
     }
 
     private static SplitPlaceholderRule createSplitPlaceholderRules(
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index c0c60ef..d033389 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -393,6 +393,7 @@
                 mEditText.setFocusableInTouchMode(false);
             }
             rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+            rootView.setFitsSystemWindows(true);
             setContentView(rootView);
 
             if (requestFocus) {
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 0e0d212..4c531b8 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -20,9 +20,12 @@
 android_test {
     name: "ConcurrentMultiSessionImeTest",
     srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
     libs: ["android.test.runner"],
     static_libs: [
         "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
         "platform-test-annotations",
         "platform-test-rules",
         "truth",
@@ -35,7 +38,11 @@
     test_suites: [
         "general-tests",
     ],
-    sdk_version: "current",
+    sdk_version: "test_current",
+
+    data: [
+        ":CtsMockInputMethod",
+    ],
 
     // Store test artifacts in separated directories for easier debugging.
     per_testcase_directory: true,
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
index 0defe5b..2e336ca 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
@@ -17,6 +17,17 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.server.inputmethod.multisessiontest">
 
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".MainActivity"
+                  android:theme="@android:style/Theme.Material.NoActionBar"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+    </application>
+
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.server.inputmethod.multisessiontest"></instrumentation>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
index fd598c5..d5ed203 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
@@ -17,13 +17,28 @@
 <configuration description="Config for Concurrent Multi-Session IME tests">
     <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
         type="module_controller">
-        <option name="required-feature" value="android.software.input_methods" />
+        <!-- TODO(b/323372972): require this feature once the bug is fixed. -->
+        <!-- option name="required-feature" value="android.software.input_methods" -->
 
         <!-- Currently enabled to automotive only -->
         <option name="required-feature" value="android.hardware.type.automotive" />
     </object>
     <option name="test-suite-tag" value="apct" />
 
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="force-install-mode" value="FULL" />
+        <option name="test-file-name" value="ConcurrentMultiSessionImeTest.apk" />
+        <option name="test-file-name" value="CtsMockInputMethod.apk" />
+    </target_preparer>
+
+    <!-- RunOnSecondaryUserTargetPreparer must run after SuiteApkInstaller. -->
+    <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
+        <option name="start-background-user" value="true" />
+        <option name="test-package-name" value="com.android.server.inputmethod.multisessiontest" />
+        <option name="test-package-name" value="com.android.cts.mockime" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
@@ -31,12 +46,6 @@
             value="settings delete secure show_ime_with_hard_keyboard" />
     </target_preparer>
 
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="force-install-mode" value="FULL" />
-        <option name="test-file-name" value="ConcurrentMultiSessionImeTest.apk" />
-    </target_preparer>
-
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.server.inputmethod.multisessiontest" />
     </test>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml
new file mode 100644
index 0000000..e16d286
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2024 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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+    <EditText
+        android:id="@+id/edit_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:hint="Input text here"/>
+</FrameLayout>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index a5ce69d..56dbde0 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -16,16 +16,27 @@
 
 package com.android.server.inputmethod.multisessiontest;
 
-import static com.google.common.truth.Truth.assertThat;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import android.content.Context;
-import android.content.pm.PackageManager;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.getResponderUserId;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.launchActivityAsUserSync;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.sendBundleAndWaitForReply;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
 
-import androidx.test.platform.app.InstrumentationRegistry;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+
+import androidx.test.core.app.ActivityScenario;
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
@@ -39,19 +50,56 @@
     @Rule
     public static final DeviceState sDeviceState = new DeviceState();
 
+    private static final ComponentName TEST_ACTIVITY = new ComponentName(
+            getInstrumentation().getTargetContext().getPackageName(),
+            MainActivity.class.getName());
+
+    private ActivityScenario<MainActivity> mActivityScenario;
+    private MainActivity mActivity;
+    private int mPeerUserId;
+
     @Before
-    public void doBeforeEachTest() {
-        // No op
+    public void setUp() {
+        // Launch passenger activity.
+        mPeerUserId = getResponderUserId();
+        launchActivityAsUserSync(TEST_ACTIVITY, mPeerUserId);
+
+        // Launch driver activity.
+        mActivityScenario = ActivityScenario.launch(MainActivity.class);
+        mActivityScenario.onActivity(activity -> mActivity = activity);
+    }
+
+    @After
+    public void tearDown() {
+        if (mActivityScenario != null) {
+            mActivityScenario.close();
+        }
     }
 
     @Test
-    public void behaviorBeingTested_expectedResult() {
-        // Sample test
-        Context context =
-                InstrumentationRegistry.getInstrumentation().getTargetContext();
-        assertThat(context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE)).isTrue();
-        assertThat(context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_INPUT_METHODS)).isTrue();
+    public void driverShowImeNotAffectPassenger() {
+        assertDriverImeHidden();
+        assertPassengerImeHidden();
+
+        showDriverImeAndAssert();
+        assertPassengerImeHidden();
+    }
+
+    private void assertDriverImeHidden() {
+        assertWithMessage("Driver IME should be hidden")
+                .that(mActivity.isMyImeVisible()).isFalse();
+    }
+
+    private void assertPassengerImeHidden() {
+        final Bundle bundleToSend = new Bundle();
+        bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_IME_STATUS);
+        Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+                mPeerUserId, bundleToSend);
+        assertWithMessage("Passenger IME should be hidden")
+                .that(receivedBundle.getInt(KEY_RESULT_CODE)).isEqualTo(REPLY_IME_HIDDEN);
+    }
+
+    private void showDriverImeAndAssert() {
+        mActivity.showMyImeAndWait();
     }
 }
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
new file mode 100644
index 0000000..e3f84c1
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.inputmethod.multisessiontest;
+
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_SHOWN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityBase;
+
+/**
+ * An {@link Activity} to test multiple concurrent session IME.
+ */
+public final class MainActivity extends ConcurrentUserActivityBase {
+    private static final String TAG = ConcurrentMultiUserTest.class.getSimpleName();
+    private static final long WAIT_IME_TIMEOUT_MS = 3000;
+
+    private EditText mEditor;
+    private InputMethodManager mImm;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display "
+                + getDisplayId());
+        setContentView(R.layout.main_activity);
+        mImm = getSystemService(InputMethodManager.class);
+        mEditor = requireViewById(R.id.edit_text);
+    }
+
+    @Override
+    protected Bundle onBundleReceived(Bundle receivedBundle) {
+        final int requestCode = receivedBundle.getInt(KEY_REQUEST_CODE);
+        Log.v(TAG, "onBundleReceived() with request code:" + requestCode);
+        final Bundle replyBundle = new Bundle();
+        switch (requestCode) {
+            case REQUEST_IME_STATUS:
+                replyBundle.putInt(KEY_RESULT_CODE,
+                        isMyImeVisible() ? REPLY_IME_SHOWN : REPLY_IME_HIDDEN);
+                break;
+            default:
+                throw new RuntimeException("Received undefined request code:" + requestCode);
+        }
+        return replyBundle;
+    }
+
+    boolean isMyImeVisible() {
+        final WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(mEditor);
+        return insets == null ? false : insets.isVisible(WindowInsetsCompat.Type.ime());
+    }
+
+    void showMyImeAndWait() {
+        Log.v(TAG, "showSoftInput");
+        runOnUiThread(() -> {
+            // requestFocus() must run on UI thread.
+            if (!mEditor.requestFocus()) {
+                Log.e(TAG, "Failed to focus on mEditor");
+                return;
+            }
+            if (!mImm.showSoftInput(mEditor, /* flags= */ 0)) {
+                Log.e(TAG, String.format("Failed to show my IME as user %d, "
+                                + "mEditor:focused=%b,hasWindowFocus=%b", getUserId(),
+                        mEditor.isFocused(), mEditor.hasWindowFocus()));
+            }
+        });
+        PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> isMyImeVisible(),
+                String.format("My IME (user %d) didn't show up", getUserId()));
+    }
+}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
new file mode 100644
index 0000000..1501bfb
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.inputmethod.multisessiontest;
+
+final class TestRequestConstants {
+    private TestRequestConstants() {
+    }
+
+    public static final String KEY_REQUEST_CODE = "key_request_code";
+    public static final String KEY_RESULT_CODE = "key_result_code";
+
+    public static final int REQUEST_IME_STATUS = 1;
+    public static final int REPLY_IME_SHOWN = 2;
+    public static final int REPLY_IME_HIDDEN = 3;
+}