Merge "[flexiglass] Always round up the remaining throttle milliseconds." into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 73a80b1..ad3e422 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -98,7 +98,10 @@
             switch (action) {
                 case Intent.ACTION_PACKAGE_RESTARTED: {
                     synchronized (mLock) {
-                        mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE);
+                        // ACTION_PACKAGE_RESTARTED doesn't always mean the app is placed and kept
+                        // in the stopped state, so don't put TRUE in the cache. Remove any existing
+                        // entry and rely on an explicit call to PackageManager's isStopped() API.
+                        mPackageStoppedState.delete(pkgUid, pkgName);
                         updateJobRestrictionsForUidLocked(pkgUid, false);
                     }
                 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 7731fac..c2c4c9e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -39298,7 +39298,7 @@
     method @Nullable public java.util.Date getKeyValidityStart();
     method @NonNull public String getKeystoreAlias();
     method public int getMaxUsageCount();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
@@ -39306,7 +39306,7 @@
     method public boolean isDevicePropertiesAttestationIncluded();
     method @NonNull public boolean isDigestsSpecified();
     method public boolean isInvalidatedByBiometricEnrollment();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isStrongBoxBacked();
     method public boolean isUnlockedDeviceRequired();
@@ -39338,7 +39338,7 @@
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -39443,14 +39443,14 @@
     method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
     method @Nullable public java.util.Date getKeyValidityStart();
     method public int getMaxUsageCount();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
     method public int getUserAuthenticationValidityDurationSeconds();
     method public boolean isDigestsSpecified();
     method public boolean isInvalidatedByBiometricEnrollment();
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified();
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUnlockedDeviceRequired();
     method public boolean isUserAuthenticationRequired();
@@ -39472,7 +39472,7 @@
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int);
-    method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
+    method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...);
     method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 777bca2..a1465df 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3248,6 +3248,7 @@
     method @Deprecated public int getDefaultNavigationPolicy();
     method public int getDevicePolicy(int);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
+    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
     method public int getLockState();
     method @Nullable public String getName();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
@@ -3281,6 +3282,7 @@
     method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
+    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 0d73e44..0253ddd 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -258,6 +258,7 @@
     // Mapping of @PolicyType to @DevicePolicy
     @NonNull private final SparseIntArray mDevicePolicies;
     @Nullable private final ComponentName mHomeComponent;
+    @Nullable private final ComponentName mInputMethodComponent;
     @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
     @Nullable private final IVirtualSensorCallback mVirtualSensorCallback;
     private final int mAudioPlaybackSessionId;
@@ -273,6 +274,7 @@
             @Nullable String name,
             @NonNull SparseIntArray devicePolicies,
             @Nullable ComponentName homeComponent,
+            @Nullable ComponentName inputMethodComponent,
             @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
             @Nullable IVirtualSensorCallback virtualSensorCallback,
             int audioPlaybackSessionId,
@@ -289,6 +291,7 @@
         mName = name;
         mDevicePolicies = Objects.requireNonNull(devicePolicies);
         mHomeComponent = homeComponent;
+        mInputMethodComponent = inputMethodComponent;
         mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
         mVirtualSensorCallback = virtualSensorCallback;
         mAudioPlaybackSessionId = audioPlaybackSessionId;
@@ -312,6 +315,7 @@
         mAudioPlaybackSessionId = parcel.readInt();
         mAudioRecordingSessionId = parcel.readInt();
         mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR);
+        mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR);
     }
 
     /**
@@ -336,6 +340,18 @@
     }
 
     /**
+     * Returns the custom component used as input method on all displays owned by this virtual
+     * device.
+     *
+     * @see Builder#setInputMethodComponent
+     */
+    @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
+    @Nullable
+    public ComponentName getInputMethodComponent() {
+        return mInputMethodComponent;
+    }
+
+    /**
      * Returns the user handles with matching managed accounts on the remote device to which
      * this virtual device is streaming.
      *
@@ -532,6 +548,7 @@
         dest.writeInt(mAudioPlaybackSessionId);
         dest.writeInt(mAudioRecordingSessionId);
         dest.writeTypedObject(mHomeComponent, flags);
+        dest.writeTypedObject(mInputMethodComponent, flags);
     }
 
     @Override
@@ -563,6 +580,8 @@
                 && Objects.equals(mActivityPolicyExemptions, that.mActivityPolicyExemptions)
                 && mDefaultActivityPolicy == that.mDefaultActivityPolicy
                 && Objects.equals(mName, that.mName)
+                && Objects.equals(mHomeComponent, that.mHomeComponent)
+                && Objects.equals(mInputMethodComponent, that.mInputMethodComponent)
                 && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
                 && mAudioRecordingSessionId == that.mAudioRecordingSessionId;
     }
@@ -572,7 +591,8 @@
         int hashCode = Objects.hash(
                 mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
                 mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
-                mDevicePolicies, mHomeComponent, mAudioPlaybackSessionId, mAudioRecordingSessionId);
+                mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId,
+                mAudioRecordingSessionId);
         for (int i = 0; i < mDevicePolicies.size(); i++) {
             hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
             hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -593,6 +613,7 @@
                 + " mName=" + mName
                 + " mDevicePolicies=" + mDevicePolicies
                 + " mHomeComponent=" + mHomeComponent
+                + " mInputMethodComponent=" + mInputMethodComponent
                 + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
                 + " mAudioRecordingSessionId=" + mAudioRecordingSessionId
                 + ")";
@@ -612,6 +633,8 @@
         pw.println(prefix + "mActivityPolicyExemptions=" + mActivityPolicyExemptions);
         pw.println(prefix + "mDevicePolicies=" + mDevicePolicies);
         pw.println(prefix + "mVirtualSensorConfigs=" + mVirtualSensorConfigs);
+        pw.println(prefix + "mHomeComponent=" + mHomeComponent);
+        pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent);
         pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
         pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId);
     }
@@ -644,16 +667,17 @@
         private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
         private boolean mDefaultActivityPolicyConfigured = false;
         @Nullable private String mName;
-        @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
+        @NonNull private final SparseIntArray mDevicePolicies = new SparseIntArray();
         private int mAudioPlaybackSessionId = AUDIO_SESSION_ID_GENERATE;
         private int mAudioRecordingSessionId = AUDIO_SESSION_ID_GENERATE;
 
-        @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+        @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
         @Nullable private Executor mVirtualSensorCallbackExecutor;
         @Nullable private VirtualSensorCallback mVirtualSensorCallback;
         @Nullable private Executor mVirtualSensorDirectChannelCallbackExecutor;
         @Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback;
         @Nullable private ComponentName mHomeComponent;
+        @Nullable private ComponentName mInputMethodComponent;
 
         private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub {
             @NonNull
@@ -749,6 +773,28 @@
         }
 
         /**
+         * Specifies a component to be used as input method on all displays owned by this virtual
+         * device.
+         *
+         * @param inputMethodComponent The component name to be used as input method. Must comply to
+         *   all general input method requirements described in the guide to
+         *   <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+         *   Creating an Input Method</a>. If the given component is not available for any user that
+         *   may interact with the virtual device, then there will effectively be no IME on this
+         *   device's displays for that user.
+         *
+         * @see android.inputmethodservice.InputMethodService
+         * @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly
+         * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker
+         */
+        @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
+        @NonNull
+        public Builder setInputMethodComponent(@Nullable ComponentName inputMethodComponent) {
+            mInputMethodComponent = inputMethodComponent;
+            return this;
+        }
+
+        /**
          * Sets the user handles with matching managed accounts on the remote device to which
          * this virtual device is streaming. The caller is responsible for verifying the presence
          * and legitimacy of a matching managed account on the remote device.
@@ -1136,6 +1182,7 @@
                     mName,
                     mDevicePolicies,
                     mHomeComponent,
+                    mInputMethodComponent,
                     mVirtualSensorConfigs,
                     virtualSensorCallbackDelegate,
                     mAudioPlaybackSessionId,
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 457fd63..51f3d8e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -379,7 +379,8 @@
 
     /**
      * If true, the requestor of the unarchival has specified that the app should be unarchived
-     * for all users.
+     * for all users. Sent as part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE}
+     * intent.
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final String EXTRA_UNARCHIVE_ALL_USERS =
@@ -396,6 +397,9 @@
      * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
      * failure dialog.
      *
+     * <p> Used as part of {@link #requestUnarchive} to return the status of the unarchival through
+     * the {@link IntentSender}.
+     *
      * @see #requestUnarchive
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
@@ -704,7 +708,8 @@
     /**
      * The installer responsible for the unarchival is disabled.
      *
-     * <p> Should only be used by the system.
+     * <p> The system will return this status if appropriate. Installers do not need to verify for
+     * this error.
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
@@ -712,7 +717,8 @@
     /**
      * The installer responsible for the unarchival has been uninstalled
      *
-     * <p> Should only be used by the system.
+     * <p> The system will return this status if appropriate. Installers do not need to verify for
+     * this error.
      */
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index 39c9400..4322bed 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -31,6 +31,7 @@
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.Executor;
 
 /**
  * Receives call backs for changes to content.
@@ -54,6 +55,7 @@
     private Transport mTransport; // guarded by mLock
 
     Handler mHandler;
+    private final Executor mExecutor;
 
     /**
      * Creates a content observer.
@@ -62,6 +64,18 @@
      */
     public ContentObserver(Handler handler) {
         mHandler = handler;
+        mExecutor = null;
+    }
+
+    /**
+     * @hide
+     * Creates a content observer with an executor.
+     *
+     * @param executor The executor to run {@link #onChange} on, or null if none.
+     * @param unused a second argument to avoid source incompatibility.
+     */
+    public ContentObserver(@Nullable Executor executor, int unused) {
+        mExecutor = executor;
     }
 
     /**
@@ -306,12 +320,19 @@
     /** @hide */
     public final void dispatchChange(boolean selfChange, @NonNull Collection<Uri> uris,
             @NotifyFlags int flags, @UserIdInt int userId) {
-        if (mHandler == null) {
-            onChange(selfChange, uris, flags, userId);
-        } else {
+        if (mExecutor != null) {
+            mExecutor.execute(() -> {
+                onChange(selfChange, uris, flags, userId);
+            });
+        } else if (mHandler != null) {
+            // Supporting Handler directly rather than wrapping in a HandlerExecutor
+            //  avoids introducing a RejectedExecutionException for legacy code when
+            //  the post fails.
             mHandler.post(() -> {
                 onChange(selfChange, uris, flags, userId);
             });
+        } else {
+            onChange(selfChange, uris, flags, userId);
         }
     }
 
diff --git a/core/java/android/database/ExecutorContentObserver.java b/core/java/android/database/ExecutorContentObserver.java
new file mode 100644
index 0000000..3ea807d
--- /dev/null
+++ b/core/java/android/database/ExecutorContentObserver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.database;
+
+import android.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ *
+ * Receives callbacks for changes to content.
+ * Must be implemented by objects which are added to a {@link ContentObservable}.
+ */
+public abstract class ExecutorContentObserver extends ContentObserver {
+    /**
+     * Creates a content observer that uses an executor for change handling.
+     *
+     * @param executor The executor to run {@link #onChange} on, or null if none.
+     */
+    public ExecutorContentObserver(@Nullable Executor executor) {
+        super(executor, 0);
+    }
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 0dc0413..28ef70b 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+    name: "mgf1_digest_setter"
+    namespace: "hardware_backed_security"
+    description: "Feature flag for mgf1 digest setter in key generation and import parameters."
+    bug: "308378912"
+}
+
+flag {
     name: "fix_unlocked_device_required_keys_v2"
     namespace: "hardware_backed_security"
     description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index f828cff..ad0e9a4 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -41,3 +41,10 @@
     description: "Prevent a task to restart based on a visible window during task switch."
     bug: "171459802"
 }
+
+flag {
+    name: "bal_respect_app_switch_state_when_check_bound_by_foreground_uid"
+    namespace: "responsible_apis"
+    description: "Prevent BAL based on it is bound by foreground Uid but the app switch is stopped."
+    bug: "171459802"
+}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 1e0b2a0..efc1455 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -80,7 +80,8 @@
                 // Suspension conditions were modified, dismiss any related visible dialogs.
                 final String[] modified = intent.getStringArrayExtra(
                         Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                if (ArrayUtils.contains(modified, mSuspendedPackage)) {
+                if (ArrayUtils.contains(modified, mSuspendedPackage)
+                        && !isPackageSuspended(mSuspendedPackage)) {
                     if (!isFinishing()) {
                         Slog.w(TAG, "Package " + mSuspendedPackage + " has modified"
                                 + " suspension conditions while dialog was visible. Finishing.");
@@ -92,6 +93,15 @@
         }
     };
 
+    private boolean isPackageSuspended(String packageName) {
+        try {
+            return mPm.isPackageSuspended(packageName);
+        } catch (PackageManager.NameNotFoundException ne) {
+            Slog.e(TAG, "Package " + packageName + " not found", ne);
+        }
+        return false;
+    }
+
     private CharSequence getAppLabel(String packageName) {
         try {
             return mPm.getApplicationInfoAsUser(packageName, 0, mUserId).loadLabel(mPm);
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 034a55a..e1c1a42 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3855,8 +3855,9 @@
              device -->
         <attr name="isVrOnly" format="boolean"/>
         <!-- Specifies if an IME can only be used on a display created by a virtual device.
-             @hide @SystemApi
-             @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") -->
+             @see android.companion.virtual.VirtualDeviceParams.Builder#setInputMethodComponent
+             @hide @SystemApi -->
+        <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") -->
         <attr name="isVirtualDeviceOnly" format="boolean"/>
         <attr name="__removed2" format="boolean" />
         <!-- Specifies whether the IME supports showing inline suggestions. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 73a7e42..1f6ac80 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6312,11 +6312,6 @@
     <!-- Content of connected display unavailable due to thermals notification. [CHAR LIMIT=NONE] -->
     <string name="connected_display_thermally_unavailable_notification_content">Your device is too warm and can\'t mirror to the display until it cools down</string>
 
-    <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] -->
-    <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string>
-    <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] -->
-    <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string>
-
     <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
     <string name="concurrent_display_notification_name">Dual screen</string>
     <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0ddb05e..a5b1028 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5107,8 +5107,6 @@
   <java-symbol type="string" name="connected_display_unavailable_notification_title"/>
   <java-symbol type="string" name="connected_display_unavailable_notification_content"/>
   <java-symbol type="string" name="connected_display_thermally_unavailable_notification_content"/>
-  <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/>
-  <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/>
   <java-symbol type="string" name="concurrent_display_notification_name"/>
   <java-symbol type="string" name="concurrent_display_notification_active_title"/>
   <java-symbol type="string" name="concurrent_display_notification_active_content"/>
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 231fa48..4982f37 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -618,7 +618,7 @@
      * @see #isMgf1DigestsSpecified()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
         if (mMgf1Digests.isEmpty()) {
             throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -633,7 +633,7 @@
      * @see #getMgf1Digests()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public boolean isMgf1DigestsSpecified() {
         return !mMgf1Digests.isEmpty();
     }
@@ -1292,7 +1292,7 @@
          * <p>See {@link KeyProperties}.{@code DIGEST} constants.
          */
         @NonNull
-        @FlaggedApi("MGF1_DIGEST_SETTER")
+        @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
         public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) {
             mMgf1Digests = Set.of(mgf1Digests);
             return this;
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c1e3bab..7b6b2d1 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -401,7 +401,7 @@
      * @see #isMgf1DigestsSpecified()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public @KeyProperties.DigestEnum Set<String> getMgf1Digests() {
         if (mMgf1Digests.isEmpty()) {
             throw new IllegalStateException("Mask generation function (MGF) not specified");
@@ -416,7 +416,7 @@
      * @see #getMgf1Digests()
      */
     @NonNull
-    @FlaggedApi("MGF1_DIGEST_SETTER")
+    @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
     public boolean isMgf1DigestsSpecified() {
         return !mMgf1Digests.isEmpty();
     }
@@ -799,7 +799,7 @@
          * <p>See {@link KeyProperties}.{@code DIGEST} constants.
          */
         @NonNull
-        @FlaggedApi("MGF1_DIGEST_SETTER")
+        @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER)
         public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) {
             mMgf1Digests = Set.of(mgf1Digests);
             return this;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index ed4b485..9c05a3a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -28,6 +28,7 @@
 import android.hardware.security.keymint.Tag;
 import android.os.Build;
 import android.os.StrictMode;
+import android.security.Flags;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
@@ -853,6 +854,22 @@
                             KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest
                     ));
                 });
+
+                /* If the MGF1 Digest setter is not set, fall back to the previous behaviour:
+                 * Add, as MGF1 Digest function, all the primary digests.
+                 * Avoid adding the default MGF1 digest as it will have been included in the
+                 * mKeymasterMgf1Digests field.
+                 */
+                if (!getMgf1DigestSetterFlag()) {
+                    final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+                            DEFAULT_MGF1_DIGEST);
+                    ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
+                        if (digest != defaultMgf1Digest) {
+                            params.add(KeyStore2ParameterUtils.makeEnum(
+                                    KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest));
+                        }
+                    });
+                }
             }
         });
         ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
@@ -928,6 +945,16 @@
         return params;
     }
 
+    private static boolean getMgf1DigestSetterFlag() {
+        try {
+            return Flags.mgf1DigestSetter();
+        } catch (SecurityException e) {
+            Log.w(TAG, "Cannot read MGF1 Digest setter flag value", e);
+            return false;
+        }
+    }
+
+
     private void addAlgorithmSpecificParameters(List<KeyParameter> params) {
         switch (mKeymasterAlgorithm) {
             case KeymasterDefs.KM_ALGORITHM_RSA:
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index ddbd93e..2d8c5a3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -25,6 +25,7 @@
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
 import android.os.StrictMode;
+import android.security.Flags;
 import android.security.GateKeeper;
 import android.security.KeyStore2;
 import android.security.KeyStoreParameter;
@@ -256,6 +257,15 @@
         }
     }
 
+    private static boolean getMgf1DigestSetterFlag() {
+        try {
+            return Flags.mgf1DigestSetter();
+        } catch (SecurityException e) {
+            Log.w(NAME, "Cannot read MGF1 Digest setter flag value", e);
+            return false;
+        }
+    }
+
     @Override
     public Date engineGetCreationDate(String alias) {
         KeyEntryResponse response = getKeyMetadata(alias);
@@ -537,11 +547,31 @@
                         /* Because of default MGF1 digest is SHA-1. It has to be added in Key
                          * characteristics. Otherwise, crypto operations will fail with Incompatible
                          * MGF1 digest.
+                         * If the MGF1 Digest setter flag isn't set, then the condition in the
+                         * if clause above must be false (cannot have MGF1 digests specified if the
+                         * flag was off). In that case, in addition to adding the default MGF1
+                         * digest, we have to add all the other digests as MGF1 Digests.
+                         *
                          */
                         importArgs.add(KeyStore2ParameterUtils.makeEnum(
                                 KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
                                 KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
                         ));
+                        if (!getMgf1DigestSetterFlag()) {
+                            final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster(
+                                    DEFAULT_MGF1_DIGEST);
+                            for (String digest : spec.getDigests()) {
+                                int digestToAddAsMgf1Digest = KeyProperties.Digest.toKeymaster(
+                                        digest);
+                                // Do not add the default MGF1 digest as it has been added above.
+                                if (digestToAddAsMgf1Digest != defaultMgf1Digest) {
+                                    importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                                            KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                                            digestToAddAsMgf1Digest
+                                    ));
+                                }
+                            }
+                        }
                     }
                 }
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 85ea809..7a3210e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -637,7 +637,7 @@
      * @return the last time this bubble was updated or accessed, whichever is most recent.
      */
     long getLastActivity() {
-        return isAppBubble() ? Long.MAX_VALUE : Math.max(mLastUpdated, mLastAccessed);
+        return Math.max(mLastUpdated, mLastAccessed);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 595a4af..bbb4b74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -784,8 +784,7 @@
         if (bubble.getPendingIntentCanceled()
                 || !(reason == Bubbles.DISMISS_AGED
                 || reason == Bubbles.DISMISS_USER_GESTURE
-                || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
-                || bubble.isAppBubble()) {
+                || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
             return;
         }
         if (DEBUG_BUBBLE_DATA) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 4bca96b..dab762f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -221,6 +221,22 @@
     }
 
     @Test
+    public void testAddAppBubble_setsTime() {
+        // Setup
+        mBubbleData.setListener(mListener);
+
+        // Test
+        assertThat(mAppBubble.getLastActivity()).isEqualTo(0);
+        setCurrentTime(1000);
+        mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
+                false /* showInShade */);
+
+        // Verify
+        assertThat(mBubbleData.getBubbleInStackWithKey(mAppBubble.getKey())).isEqualTo(mAppBubble);
+        assertThat(mAppBubble.getLastActivity()).isEqualTo(1000);
+    }
+
+    @Test
     public void testRemoveBubble() {
         // Setup
         sendUpdatedEntryAtTime(mEntryA1, 1000);
@@ -1162,7 +1178,7 @@
     }
 
     @Test
-    public void test_removeAppBubble_skipsOverflow() {
+    public void test_removeAppBubble_overflows() {
         String appBubbleKey = mAppBubble.getKey();
         mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
                 false /* showInShade */);
@@ -1170,7 +1186,7 @@
 
         mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
 
-        assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+        assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isEqualTo(mAppBubble);
         assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
     }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c52a89c..2970aaa 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -138,6 +138,13 @@
 }
 
 flag {
+   name: "qs_new_tiles"
+   namespace: "systemui"
+   description: "Use the new tiles in the Quick Settings. Should have no behavior changes."
+   bug: "241772429"
+}
+
+flag {
     name: "coroutine_tracing"
     namespace: "systemui"
     description: "Adds thread-local data to System UI's global coroutine scopes to "
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
index 5d8eaf7..58052cd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -2,10 +2,9 @@
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.unit.IntSize
 
 interface DraggableHandler {
-    fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int = 1)
+    fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int = 1)
     fun onDelta(pixels: Float)
     fun onDragStopped(velocity: Float)
 }
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 a0fba80..3873878 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
@@ -66,8 +66,8 @@
     orientation: Orientation,
     enabled: Boolean,
     startDragImmediately: Boolean,
-    onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
-    onDragDelta: (Float) -> Unit,
+    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
+    onDragDelta: (delta: Float) -> Unit,
     onDragStopped: (velocity: Float) -> Unit,
 ): Modifier =
     this.then(
@@ -86,7 +86,7 @@
     private val enabled: Boolean,
     private val startDragImmediately: Boolean,
     private val onDragStarted:
-        (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     private val onDragDelta: (Float) -> Unit,
     private val onDragStopped: (velocity: Float) -> Unit,
 ) : ModifierNodeElement<MultiPointerDraggableNode>() {
@@ -114,7 +114,7 @@
     orientation: Orientation,
     enabled: Boolean,
     var startDragImmediately: Boolean,
-    var onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
+    var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     var onDragDelta: (Float) -> Unit,
     var onDragStopped: (velocity: Float) -> Unit,
 ) : PointerInputModifierNode, DelegatingNode(), CompositionLocalConsumerModifierNode {
@@ -153,9 +153,9 @@
             return
         }
 
-        val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown ->
+        val onDragStart: (Offset, Float, Int) -> Unit = { startedPosition, overSlop, pointersDown ->
             velocityTracker.resetTracking()
-            onDragStarted(size, startedPosition, pointersDown)
+            onDragStarted(startedPosition, overSlop, pointersDown)
         }
 
         val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
@@ -203,7 +203,7 @@
 private suspend fun PointerInputScope.detectDragGestures(
     orientation: Orientation,
     startDragImmediately: () -> Boolean,
-    onDragStart: (startedPosition: Offset, pointersDown: Int) -> Unit,
+    onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     onDragEnd: () -> Unit,
     onDragCancel: () -> Unit,
     onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
@@ -241,7 +241,7 @@
                 }
             }
 
-            onDragStart(drag.position, pressed.size)
+            onDragStart(drag.position, overSlop, pressed.size)
             onDrag(drag, overSlop)
 
             val successful =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 2986504..ded6cc1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -44,22 +44,22 @@
      * Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
      * gesture begins at the edge of the scrollable component (so that a scroll in that direction
      * can no longer be consumed). If the gesture is partially consumed by the scrollable component,
-     * there will be NO overscroll effect between scenes.
+     * there will be NO preview of the next scene.
      *
      * In addition, during scene transitions, scroll events are consumed by the
      * [SceneTransitionLayout] instead of the scrollable component.
      */
-    EdgeNoOverscroll(canStartOnPostFling = false),
+    EdgeNoPreview(canStartOnPostFling = false),
 
     /**
      * Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
      * gesture begins at the edge of the scrollable component. If the gesture is partially consumed
-     * by the scrollable component, there will be an overscroll effect between scenes.
+     * by the scrollable component, there will be a preview of the next scene.
      *
      * In addition, during scene transitions, scroll events are consumed by the
      * [SceneTransitionLayout] instead of the scrollable component.
      */
-    EdgeWithOverscroll(canStartOnPostFling = true),
+    EdgeWithPreview(canStartOnPostFling = true),
 
     /**
      * Any overscroll will be used by the [SceneTransitionLayout] to move to the next scene.
@@ -67,7 +67,7 @@
      * In addition, during scene transitions, scroll events are consumed by the
      * [SceneTransitionLayout] instead of the scrollable component.
      */
-    Always(canStartOnPostFling = true),
+    EdgeAlways(canStartOnPostFling = true),
 }
 
 internal fun Modifier.nestedScrollToScene(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index b00c886..212c9eb6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("NOTHING_TO_INLINE")
+
 package com.android.compose.animation.scene
 
 import android.util.Log
@@ -26,7 +28,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -48,14 +49,13 @@
             layoutImpl.state.transitionState = value
         }
 
-    /**
-     * The transition controlled by this gesture handler. It will be set as the [transitionState] in
-     * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition.
-     *
-     * Note: the initialScene here does not matter, it's only used for initializing the transition
-     * and will be replaced when a drag event starts.
-     */
-    internal val swipeTransition = SwipeTransition(initialScene = currentScene)
+    internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f)
+        private set
+
+    private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
+        if (isDrivingTransition || force) transitionState = newTransition
+        swipeTransition = newTransition
+    }
 
     internal val currentScene: Scene
         get() = layoutImpl.scene(transitionState.currentScene)
@@ -63,15 +63,6 @@
     internal val isDrivingTransition
         get() = transitionState == swipeTransition
 
-    internal var isAnimatingOffset
-        get() = swipeTransition.isAnimatingOffset
-        private set(value) {
-            swipeTransition.isAnimatingOffset = value
-        }
-
-    internal val swipeTransitionToScene
-        get() = swipeTransition._toScene
-
     /**
      * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
      * as SwipeableV2Defaults.VelocityThreshold.
@@ -86,11 +77,17 @@
 
     internal var gestureWithPriority: Any? = null
 
-    internal fun onDragStarted(pointersDown: Int, layoutSize: IntSize, startedPosition: Offset?) {
+    /** The [UserAction]s associated to the current swipe. */
+    private var actionUpOrLeft: UserAction? = null
+    private var actionDownOrRight: UserAction? = null
+    private var actionUpOrLeftNoEdge: UserAction? = null
+    private var actionDownOrRightNoEdge: UserAction? = null
+
+    internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
         if (isDrivingTransition) {
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
-            swipeTransition.stopOffsetAnimation()
+            swipeTransition.cancelOffsetAnimation()
             return
         }
 
@@ -106,37 +103,29 @@
         }
 
         val fromScene = currentScene
+        setCurrentActions(fromScene, startedPosition, pointersDown)
 
-        swipeTransition._currentScene = fromScene
-        swipeTransition._fromScene = fromScene
+        if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) {
+            return
+        }
 
-        // We don't know where we are transitioning to yet given that the drag just started, so set
-        // it to fromScene, which will effectively be treated the same as Idle(fromScene).
-        swipeTransition._toScene = fromScene
+        val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop)
 
-        swipeTransition.stopOffsetAnimation()
-        swipeTransition.dragOffset = 0f
+        updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
+    }
 
-        // Use the layout size in the swipe orientation for swipe distance.
-        // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances,
-        // we will also have to make sure that we correctly handle overscroll.
-        swipeTransition.absoluteDistance =
-            when (orientation) {
-                Orientation.Horizontal -> layoutSize.width
-                Orientation.Vertical -> layoutSize.height
-            }.toFloat()
-
+    private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
         val fromEdge =
             startedPosition?.let { position ->
                 layoutImpl.edgeDetector.edge(
-                    layoutSize,
+                    fromScene.targetSize,
                     position.round(),
                     layoutImpl.density,
                     orientation,
                 )
             }
 
-        swipeTransition.actionUpOrLeft =
+        val upOrLeft =
             Swipe(
                 direction =
                     when (orientation) {
@@ -147,7 +136,7 @@
                 fromEdge = fromEdge,
             )
 
-        swipeTransition.actionDownOrRight =
+        val downOrRight =
             Swipe(
                 direction =
                     when (orientation) {
@@ -159,108 +148,114 @@
             )
 
         if (fromEdge == null) {
-            swipeTransition.actionUpOrLeftNoEdge = null
-            swipeTransition.actionDownOrRightNoEdge = null
+            actionUpOrLeft = null
+            actionDownOrRight = null
+            actionUpOrLeftNoEdge = upOrLeft
+            actionDownOrRightNoEdge = downOrRight
         } else {
-            swipeTransition.actionUpOrLeftNoEdge =
-                (swipeTransition.actionUpOrLeft as Swipe).copy(fromEdge = null)
-            swipeTransition.actionDownOrRightNoEdge =
-                (swipeTransition.actionDownOrRight as Swipe).copy(fromEdge = null)
-        }
-
-        if (swipeTransition.absoluteDistance > 0f) {
-            transitionState = swipeTransition
+            actionUpOrLeft = upOrLeft
+            actionDownOrRight = downOrRight
+            actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null)
+            actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null)
         }
     }
 
-    internal fun onDrag(delta: Float) {
-        if (delta == 0f) return
+    /**
+     * Use the layout size in the swipe orientation for swipe distance.
+     *
+     * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
+     *   will also have to make sure that we correctly handle overscroll.
+     */
+    private fun Scene.getAbsoluteDistance(): Float {
+        return when (orientation) {
+            Orientation.Horizontal -> targetSize.width
+            Orientation.Vertical -> targetSize.height
+        }.toFloat()
+    }
 
+    internal fun onDrag(delta: Float) {
+        if (delta == 0f || !isDrivingTransition) return
         swipeTransition.dragOffset += delta
 
-        // First check transition.fromScene should be changed for the case where the user quickly
-        // swiped twice in a row to accelerate the transition and go from A => B then B => C really
-        // fast.
-        maybeHandleAcceleratedSwipe()
-
-        val offset = swipeTransition.dragOffset
-        val fromScene = swipeTransition._fromScene
+        val (fromScene, acceleratedOffset) =
+            computeFromSceneConsideringAcceleratedSwipe(swipeTransition)
+        swipeTransition.dragOffset += acceleratedOffset
 
         // Compute the target scene depending on the current offset.
-        val target = fromScene.findTargetSceneAndDistance(offset)
+        val (targetScene, distance) =
+            fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset)
 
-        if (swipeTransition._toScene.key != target.sceneKey) {
-            swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
-        }
-
-        if (swipeTransition._distance != target.distance) {
-            swipeTransition._distance = target.distance
+        // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes
+        if (
+            fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene
+        ) {
+            updateTransition(
+                SwipeTransition(fromScene, targetScene, distance).apply {
+                    this.dragOffset = swipeTransition.dragOffset
+                }
+            )
         }
     }
 
     /**
      * Change fromScene in the case where the user quickly swiped multiple times in the same
      * direction to accelerate the transition from A => B then B => C.
+     *
+     * @return the new fromScene and a dragOffset to be added in case the scene has changed
+     *
+     * TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging twice
+     *   before B has been reached
      */
-    private fun maybeHandleAcceleratedSwipe() {
+    private inline fun computeFromSceneConsideringAcceleratedSwipe(
+        swipeTransition: SwipeTransition,
+    ): Pair<Scene, Float> {
         val toScene = swipeTransition._toScene
         val fromScene = swipeTransition._fromScene
+        val absoluteDistance = swipeTransition.distance.absoluteValue
 
         // If the swipe was not committed, don't do anything.
         if (fromScene == toScene || swipeTransition._currentScene != toScene) {
-            return
+            return Pair(fromScene, 0f)
         }
 
         // If the offset is past the distance then let's change fromScene so that the user can swipe
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
-        val absoluteDistance = swipeTransition.absoluteDistance
-        if (offset <= -absoluteDistance && swipeTransition.upOrLeft(fromScene) == toScene.key) {
-            swipeTransition.dragOffset += absoluteDistance
-            swipeTransition._fromScene = toScene
-        } else if (
-            offset >= absoluteDistance && swipeTransition.downOrRight(fromScene) == toScene.key
-        ) {
-            swipeTransition.dragOffset -= absoluteDistance
-            swipeTransition._fromScene = toScene
+        return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) {
+            Pair(toScene, absoluteDistance)
+        } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) {
+            Pair(toScene, -absoluteDistance)
+        } else {
+            Pair(fromScene, 0f)
         }
-
-        // Important note: toScene and distance will be updated right after this function is called,
-        // using fromScene and dragOffset.
     }
 
-    private class TargetScene(
-        val sceneKey: SceneKey,
-        val distance: Float,
-    )
-
-    private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene {
-        val upOrLeft = swipeTransition.upOrLeft(this)
-        val downOrRight = swipeTransition.downOrRight(this)
+    // TODO(b/290184746): there are two bugs here:
+    // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash
+    // 2. if one of them changes during a transition, the transition will jump cut to the new target
+    private inline fun Scene.findTargetSceneAndDistance(
+        directionOffset: Float
+    ): Pair<Scene, Float> {
+        val upOrLeft = upOrLeft()
+        val downOrRight = downOrRight()
+        val absoluteDistance = getAbsoluteDistance()
 
         // Compute the target scene depending on the current offset.
-        return when {
-            directionOffset < 0f && upOrLeft != null -> {
-                TargetScene(
-                    sceneKey = upOrLeft,
-                    distance = -swipeTransition.absoluteDistance,
-                )
-            }
-            directionOffset > 0f && downOrRight != null -> {
-                TargetScene(
-                    sceneKey = downOrRight,
-                    distance = swipeTransition.absoluteDistance,
-                )
-            }
-            else -> {
-                TargetScene(
-                    sceneKey = key,
-                    distance = 0f,
-                )
-            }
+        return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) {
+            Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance)
+        } else {
+            Pair(layoutImpl.scene(downOrRight), absoluteDistance)
         }
     }
 
+    private fun Scene.upOrLeft(): SceneKey? {
+        return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge]
+    }
+
+    private fun Scene.downOrRight(): SceneKey? {
+        return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge]
+    }
+
     internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
         // The state was changed since the drag started; don't do anything.
         if (!isDrivingTransition) {
@@ -291,11 +286,6 @@
             // velocity and offset of the transition, then we launch the animation.
 
             val toScene = swipeTransition._toScene
-            if (fromScene == toScene) {
-                // We were not animating.
-                transitionState = TransitionState.Idle(fromScene.key)
-                return
-            }
 
             // Compute the destination scene (and therefore offset) to settle in.
             val offset = swipeTransition.dragOffset
@@ -322,12 +312,14 @@
 
             if (startFromIdlePosition) {
                 // If there is a next scene, we start the overscroll animation.
-                val target = fromScene.findTargetSceneAndDistance(velocity)
-                val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+                val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity)
+                val isValidTarget = distance != 0f && targetScene.key != fromScene.key
                 if (isValidTarget) {
-                    swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
-                    swipeTransition._distance = target.distance
-
+                    updateTransition(
+                        SwipeTransition(fromScene, targetScene, distance).apply {
+                            _currentScene = swipeTransition._currentScene
+                        }
+                    )
                     animateTo(targetScene = fromScene, targetOffset = 0f)
                 } else {
                     // We will not animate
@@ -382,10 +374,10 @@
     ) {
         swipeTransition.startOffsetAnimation {
             coroutineScope.launch {
-                if (!isAnimatingOffset) {
+                if (!swipeTransition.isAnimatingOffset) {
                     swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
                 }
-                isAnimatingOffset = true
+                swipeTransition.isAnimatingOffset = true
 
                 swipeTransition.offsetAnimatable.animateTo(
                     targetOffset,
@@ -397,7 +389,7 @@
                     initialVelocity = initialVelocity,
                 )
 
-                isAnimatingOffset = false
+                swipeTransition.finishOffsetAnimation()
 
                 // Now that the animation is done, the state should be idle. Note that if the state
                 // was changed since this animation started, some external code changed it and we
@@ -410,29 +402,26 @@
         }
     }
 
-    internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
-        var _currentScene by mutableStateOf(initialScene)
+    internal class SwipeTransition(
+        val _fromScene: Scene,
+        val _toScene: Scene,
+        /**
+         * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
+         * above or to the left of [toScene].
+         */
+        val distance: Float
+    ) : TransitionState.Transition {
+        var _currentScene by mutableStateOf(_fromScene)
         override val currentScene: SceneKey
             get() = _currentScene.key
 
-        var _fromScene by mutableStateOf(initialScene)
-        override val fromScene: SceneKey
-            get() = _fromScene.key
+        override val fromScene: SceneKey = _fromScene.key
 
-        var _toScene by mutableStateOf(initialScene)
-        override val toScene: SceneKey
-            get() = _toScene.key
+        override val toScene: SceneKey = _toScene.key
 
         override val progress: Float
             get() {
                 val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
-                if (distance == 0f) {
-                    // This can happen only if fromScene == toScene.
-                    error(
-                        "Transition.progress should be called only when Transition.fromScene != " +
-                            "Transition.toScene"
-                    )
-                }
                 return offset / distance
             }
 
@@ -459,46 +448,22 @@
 
         /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
         fun startOffsetAnimation(job: () -> Job) {
-            stopOffsetAnimation()
+            cancelOffsetAnimation()
             offsetAnimationJob = job()
         }
 
-        /** Stops any ongoing offset animation. */
-        fun stopOffsetAnimation() {
+        /** Cancel any ongoing offset animation. */
+        fun cancelOffsetAnimation() {
             offsetAnimationJob?.cancel()
+            finishOffsetAnimation()
+        }
 
+        fun finishOffsetAnimation() {
             if (isAnimatingOffset) {
                 isAnimatingOffset = false
                 dragOffset = offsetAnimatable.value
             }
         }
-
-        /** The absolute distance between [fromScene] and [toScene]. */
-        var absoluteDistance = 0f
-
-        /**
-         * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
-         * above or to the left of [toScene].
-         */
-        var _distance by mutableFloatStateOf(0f)
-        val distance: Float
-            get() = _distance
-
-        /** The [UserAction]s associated to this swipe. */
-        var actionUpOrLeft: UserAction = Back
-        var actionDownOrRight: UserAction = Back
-        var actionUpOrLeftNoEdge: UserAction? = null
-        var actionDownOrRightNoEdge: UserAction? = null
-
-        fun upOrLeft(scene: Scene): SceneKey? {
-            return scene.userActions[actionUpOrLeft]
-                ?: actionUpOrLeftNoEdge?.let { scene.userActions[it] }
-        }
-
-        fun downOrRight(scene: Scene): SceneKey? {
-            return scene.userActions[actionDownOrRight]
-                ?: actionDownOrRightNoEdge?.let { scene.userActions[it] }
-        }
     }
 
     companion object {
@@ -509,9 +474,9 @@
 private class SceneDraggableHandler(
     private val gestureHandler: SceneGestureHandler,
 ) : DraggableHandler {
-    override fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) {
+    override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) {
         gestureHandler.gestureWithPriority = this
-        gestureHandler.onDragStarted(pointersDown, layoutSize, startedPosition)
+        gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop)
     }
 
     override fun onDelta(pixels: Float) {
@@ -589,7 +554,7 @@
                 // The progress value can go beyond this range in the case of overscroll.
                 val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
                 if (shouldSnapToIdle) {
-                    gestureHandler.swipeTransition.stopOffsetAnimation()
+                    gestureHandler.swipeTransition.cancelOffsetAnimation()
                     gestureHandler.transitionState =
                         TransitionState.Idle(gestureHandler.swipeTransition.currentScene)
                 }
@@ -612,15 +577,15 @@
                         canChangeScene = false // unused: added for consistency
                         false
                     }
-                    NestedScrollBehavior.EdgeNoOverscroll -> {
+                    NestedScrollBehavior.EdgeNoPreview -> {
                         canChangeScene = isZeroOffset
                         isZeroOffset && hasNextScene(offsetAvailable)
                     }
-                    NestedScrollBehavior.EdgeWithOverscroll -> {
+                    NestedScrollBehavior.EdgeWithPreview -> {
                         canChangeScene = isZeroOffset
                         hasNextScene(offsetAvailable)
                     }
-                    NestedScrollBehavior.Always -> {
+                    NestedScrollBehavior.EdgeAlways -> {
                         canChangeScene = true
                         hasNextScene(offsetAvailable)
                     }
@@ -639,12 +604,12 @@
                 behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
             },
             canContinueScroll = { true },
-            onStart = {
+            onStart = { offsetAvailable ->
                 gestureHandler.gestureWithPriority = this
                 gestureHandler.onDragStarted(
                     pointersDown = 1,
-                    layoutSize = gestureHandler.currentScene.targetSize,
                     startedPosition = null,
+                    overSlop = offsetAvailable,
                 )
             },
             onScroll = { offsetAvailable ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 07add77..afa184b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -132,8 +132,8 @@
      */
     fun Modifier.nestedScrollToScene(
         orientation: Orientation,
-        startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll,
-        endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll,
+        startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
     ): Modifier
 
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 2c78dee..116a666 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -46,7 +46,7 @@
         // user can't swipe in the other direction.
         startDragImmediately =
             gestureHandler.isDrivingTransition &&
-                gestureHandler.isAnimatingOffset &&
+                gestureHandler.swipeTransition.isAnimatingOffset &&
                 !canOppositeSwipe,
         onDragStarted = gestureHandler.draggable::onDragStarted,
         onDragDelta = gestureHandler.draggable::onDelta,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index a5fd1bf..c49a2b8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -38,7 +38,7 @@
     private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
     private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
     private val canContinueScroll: () -> Boolean,
-    private val onStart: () -> Unit,
+    private val onStart: (offsetAvailable: Offset) -> Unit,
     private val onScroll: (offsetAvailable: Offset) -> Offset,
     private val onStop: (velocityAvailable: Velocity) -> Velocity,
 ) : NestedScrollConnection {
@@ -131,7 +131,7 @@
 
         // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
         // lifted (step 3b), or this object has been destroyed (step 3c).
-        onStart()
+        onStart(available)
 
         return onScroll(available)
     }
@@ -156,7 +156,7 @@
     canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
     canStartPostFling: (velocityAvailable: Float) -> Boolean,
     canContinueScroll: () -> Boolean,
-    onStart: () -> Unit,
+    onStart: (offsetAvailable: Float) -> Unit,
     onScroll: (offsetAvailable: Float) -> Float,
     onStop: (velocityAvailable: Float) -> Float,
 ) =
@@ -172,7 +172,7 @@
                 canStartPostFling(velocityAvailable.toFloat())
             },
             canContinueScroll = canContinueScroll,
-            onStart = onStart,
+            onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) },
             onScroll = { offsetAvailable: Offset ->
                 onScroll(offsetAvailable.toFloat()).toOffset()
             },
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index b84cb36..aa942e0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -29,10 +29,10 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.NestedScrollBehavior.Always
 import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
-import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoOverscroll
-import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithOverscroll
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeAlways
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoPreview
+import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithPreview
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
@@ -191,8 +191,13 @@
         runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
     }
 
-    private fun DraggableHandler.onDragStarted() {
-        onDragStarted(layoutSize = LAYOUT_SIZE, startedPosition = Offset.Zero)
+    private fun DraggableHandler.onDragStarted(
+        overSlop: Float = 0f,
+        startedPosition: Offset = Offset.Zero,
+    ) {
+        onDragStarted(startedPosition, overSlop)
+        // MultiPointerDraggable will always call onDelta with the initial overSlop right after
+        onDelta(overSlop)
     }
 
     @Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) }
@@ -291,6 +296,86 @@
     }
 
     @Test
+    fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
+        horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f))
+        assertIdle(currentScene = SceneA)
+        horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f))
+        assertIdle(currentScene = SceneA)
+    }
+
+    @Test
+    fun onDragIntoNoAction_startTransitionToOppositeDirection() = runGestureTest {
+        navigateToSceneC()
+
+        // We are on SceneC which has no action in Down direction
+        draggable.onDragStarted(down(0.1f))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            progress = -0.1f
+        )
+
+        // Reverse drag direction, it will consume the previous drag
+        draggable.onDelta(up(0.1f))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            progress = 0.0f
+        )
+
+        // Continue reverse drag direction, it should record progress to Scene B
+        draggable.onDelta(up(0.1f))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            progress = 0.1f
+        )
+    }
+
+    @Test
+    fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest {
+        navigateToSceneC()
+
+        // Start dragging from the bottom
+        draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneA,
+            progress = 0.1f
+        )
+    }
+
+    @Test
+    fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
+        draggable.onDragStarted(down(0.3f))
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneC,
+            progress = 0.3f
+        )
+        draggable.onDelta(up(0.3f))
+        assertTransition(
+            currentScene = SceneA,
+            fromScene = SceneA,
+            toScene = SceneC,
+            progress = 0.0f
+        )
+    }
+
+    private fun TestGestureScope.navigateToSceneC() {
+        assertIdle(currentScene = SceneA)
+        draggable.onDragStarted(down(1f))
+        draggable.onDragStopped(0f)
+        advanceUntilIdle()
+        assertIdle(currentScene = SceneC)
+    }
+
+    @Test
     fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
         draggable.onDragStarted()
@@ -339,29 +424,29 @@
         )
 
         // The stop animation is not started yet
-        assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
 
         runCurrent()
 
-        assertThat(sceneGestureHandler.isAnimatingOffset).isTrue()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
         assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
         assertTransition(currentScene = SceneC)
 
         // Start a new gesture while the offset is animating
         draggable.onDragStarted()
-        assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
     }
 
     @Test
     fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun onPostScrollWithNothingAvailable_EdgeWithOverscroll_doNotChangeState() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         val consumed =
             nestedScroll.onPostScroll(
                 consumed = Offset.Zero,
@@ -375,7 +460,7 @@
 
     @Test
     fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         val consumed =
             nestedScroll.onPostScroll(
                 consumed = Offset.Zero,
@@ -404,7 +489,7 @@
 
     @Test
     fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.scroll(available = offsetY10)
         assertTransition(currentScene = SceneA)
 
@@ -432,7 +517,7 @@
         firstScroll: Float,
         secondScroll: Float
     ) {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         // start scene transition
         nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll))
 
@@ -485,7 +570,7 @@
 
     @Test
     fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.scroll(available = offsetY10)
         assertTransition(currentScene = SceneA)
 
@@ -517,7 +602,7 @@
 
     @Test
     fun flingAfterScroll_EdgeNoOverscroll_goToNextScene() = runGestureTest {
-        flingAfterScroll(use = EdgeNoOverscroll, idleAfterScroll = false)
+        flingAfterScroll(use = EdgeNoPreview, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -528,7 +613,7 @@
 
     @Test
     fun flingAfterScroll_EdgeWithOverscroll_goToNextScene() = runGestureTest {
-        flingAfterScroll(use = EdgeWithOverscroll, idleAfterScroll = false)
+        flingAfterScroll(use = EdgeWithPreview, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -539,7 +624,7 @@
 
     @Test
     fun flingAfterScroll_Always_goToNextScene() = runGestureTest {
-        flingAfterScroll(use = Always, idleAfterScroll = false)
+        flingAfterScroll(use = EdgeAlways, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -573,14 +658,14 @@
 
     @Test
     fun flingAfterScrollStartedInScene_EdgeNoOverscroll_doNothing() = runGestureTest {
-        flingAfterScrollStartedInScene(use = EdgeNoOverscroll, idleAfterScroll = true)
+        flingAfterScrollStartedInScene(use = EdgeNoPreview, idleAfterScroll = true)
 
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun flingAfterScrollStartedInScene_EdgeWithOverscroll_doOverscrollAnimation() = runGestureTest {
-        flingAfterScrollStartedInScene(use = EdgeWithOverscroll, idleAfterScroll = false)
+        flingAfterScrollStartedInScene(use = EdgeWithPreview, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneA)
 
@@ -591,7 +676,7 @@
 
     @Test
     fun flingAfterScrollStartedInScene_Always_goToNextScene() = runGestureTest {
-        flingAfterScrollStartedInScene(use = Always, idleAfterScroll = false)
+        flingAfterScrollStartedInScene(use = EdgeAlways, idleAfterScroll = false)
 
         assertTransition(currentScene = SceneC)
 
@@ -614,14 +699,14 @@
 
     @Test
     fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
         assertIdle(currentScene = SceneA)
     }
 
     @Test
     fun startNestedScrollWhileDragging() = runGestureTest {
-        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always)
+        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
         draggable.onDragStarted()
         assertTransition(currentScene = SceneA)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 97ac8c6..d3049d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -6,6 +6,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -38,6 +39,7 @@
     private val testUtils = SceneTestUtils(this)
     private val testScope = testUtils.testScope
     private val userRepository = FakeUserRepository()
+    private val keyguardRepository = FakeKeyguardRepository()
 
     private lateinit var underTest: DeviceEntryRepository
 
@@ -55,6 +57,7 @@
                 lockPatternUtils = lockPatternUtils,
                 keyguardBypassController = keyguardBypassController,
                 keyguardStateController = keyguardStateController,
+                keyguardRepository = keyguardRepository,
             )
         testScope.runCurrent()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 8c896a6..1e2784a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -25,11 +25,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
+import com.android.systemui.Flags.FLAG_QS_NEW_TILES
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.nano.SystemUIProtoDump
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.BooleanState
 import com.android.systemui.qs.FakeQSFactory
@@ -81,8 +80,7 @@
     private val tileFactory = FakeQSFactory(::tileCreator)
     private val customTileAddedRepository: CustomTileAddedRepository =
         FakeCustomTileAddedRepository()
-    private val featureFlags = FakeFeatureFlags()
-    private val pipelineFlags = QSPipelineFlagsRepository(featureFlags)
+    private val pipelineFlags = QSPipelineFlagsRepository()
     private val tileLifecycleManagerFactory = TLMFactory()
 
     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
@@ -100,14 +98,12 @@
 
     private lateinit var underTest: CurrentTilesInteractorImpl
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
         mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
-        // TODO(b/299909337): Add test checking the new factory is used when the flag is on
-        featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true)
+        mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
 
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 24cd9b5..b1a153a 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -66,8 +66,6 @@
     public static final int MODE_ON = 1;
     public static final int MODE_OFF = 2;
     public static final int MODE_ESTIMATE = 3;
-    @VisibleForTesting
-    public static final long LAYOUT_TRANSITION_DURATION = 200;
 
     private final AccessorizedBatteryDrawable mDrawable;
     private final ImageView mBatteryIconView;
@@ -136,7 +134,7 @@
 
     private void setupLayoutTransition() {
         LayoutTransition transition = new LayoutTransition();
-        transition.setDuration(LAYOUT_TRANSITION_DURATION);
+        transition.setDuration(200);
 
         // Animates appearing/disappearing of the battery percentage text using fade-in/fade-out
         // and disables all other animation types
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index cd764c0..b915418 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,6 +1,5 @@
 package com.android.systemui.deviceentry
 
-import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import dagger.Module
@@ -10,7 +9,6 @@
     includes =
         [
             DeviceEntryRepositoryModule::class,
-            DeviceEntryHapticsRepositoryModule::class,
         ],
 )
 abstract class DeviceEntryModule {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
deleted file mode 100644
index 1458404..0000000
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 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.deviceentry.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Interface for classes that can access device-entry haptics application state. */
-interface DeviceEntryHapticsRepository {
-    /**
-     * Whether a successful biometric haptic has been requested. Has not yet been handled if true.
-     */
-    val successHapticRequest: Flow<Boolean>
-
-    /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */
-    val errorHapticRequest: Flow<Boolean>
-
-    fun requestSuccessHaptic()
-    fun handleSuccessHaptic()
-    fun requestErrorHaptic()
-    fun handleErrorHaptic()
-}
-
-/** Encapsulates application state for device entry haptics. */
-@SysUISingleton
-class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository {
-    private val _successHapticRequest = MutableStateFlow(false)
-    override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
-
-    private val _errorHapticRequest = MutableStateFlow(false)
-    override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
-
-    override fun requestSuccessHaptic() {
-        _successHapticRequest.value = true
-    }
-
-    override fun handleSuccessHaptic() {
-        _successHapticRequest.value = false
-    }
-
-    override fun requestErrorHaptic() {
-        _errorHapticRequest.value = true
-    }
-
-    override fun handleErrorHaptic() {
-        _errorHapticRequest.value = false
-    }
-}
-
-@Module
-interface DeviceEntryHapticsRepositoryModule {
-    @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository
-}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index f27bbe6..08e8c2d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -7,26 +7,36 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+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.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.sample
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
 /** Interface for classes that can access device-entry-related application state. */
 interface DeviceEntryRepository {
+    /** Whether the device is immediately entering the device after a biometric unlock. */
+    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource>
+
     /**
      * Whether the device is unlocked.
      *
@@ -73,7 +83,14 @@
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardBypassController: KeyguardBypassController,
     keyguardStateController: KeyguardStateController,
+    keyguardRepository: KeyguardRepository,
 ) : DeviceEntryRepository {
+    override val enteringDeviceFromBiometricUnlock =
+        keyguardRepository.biometricUnlockState
+            .filter { BiometricUnlockModel.dismissesKeyguard(it) }
+            .sample(
+                keyguardRepository.biometricUnlockSource.filterNotNull(),
+            )
 
     private val _isUnlocked = MutableStateFlow(false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
new file mode 100644
index 0000000..1a6bd04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.DeviceEntryBiometricMode
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/** Business logic for device entry biometric states that may differ based on the biometric mode. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryBiometricAuthInteractor
+@Inject
+constructor(
+    biometricSettingsRepository: BiometricSettingsRepository,
+    deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+) {
+    private val biometricMode: Flow<DeviceEntryBiometricMode> =
+        combine(
+            biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+            biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+        ) { fingerprintEnrolled, faceEnrolled ->
+            if (fingerprintEnrolled && faceEnrolled) {
+                DeviceEntryBiometricMode.CO_EXPERIENCE
+            } else if (fingerprintEnrolled) {
+                DeviceEntryBiometricMode.FINGERPRINT_ONLY
+            } else if (faceEnrolled) {
+                DeviceEntryBiometricMode.FACE_ONLY
+            } else {
+                DeviceEntryBiometricMode.NONE
+            }
+        }
+    private val faceOnly: Flow<Boolean> =
+        biometricMode.map { it == DeviceEntryBiometricMode.FACE_ONLY }
+
+    /**
+     * Triggered if face is the only biometric that can be used for device entry and a face failure
+     * occurs.
+     */
+    val faceOnlyFaceFailure: Flow<FailedFaceAuthenticationStatus> =
+        faceOnly.flatMapLatest { faceOnly ->
+            if (faceOnly) {
+                deviceEntryFaceAuthInteractor.faceFailure
+            } else {
+                emptyFlow()
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
new file mode 100644
index 0000000..70716c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterIsInstance
+
+@SysUISingleton
+class DeviceEntryFaceAuthInteractor
+@Inject
+constructor(
+    repository: DeviceEntryFaceAuthRepository,
+) {
+    val faceFailure: Flow<FailedFaceAuthenticationStatus> =
+        repository.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
new file mode 100644
index 0000000..efa1c0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterIsInstance
+
+@SysUISingleton
+class DeviceEntryFingerprintAuthInteractor
+@Inject
+constructor(
+    repository: DeviceEntryFingerprintAuthRepository,
+) {
+    val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
+        repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 53d6f73..649a971 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository
 import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -34,11 +33,12 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 
 /**
  * Business logic for device entry haptic events. Determines whether the haptic should play. In
- * particular, there are extra guards for whether device entry error and successes hatpics should
+ * particular, there are extra guards for whether device entry error and successes haptics should
  * play when the physical fingerprint sensor is located on the power button.
  */
 @ExperimentalCoroutinesApi
@@ -46,7 +46,9 @@
 class DeviceEntryHapticsInteractor
 @Inject
 constructor(
-    private val repository: DeviceEntryHapticsRepository,
+    deviceEntryInteractor: DeviceEntryInteractor,
+    deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+    deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
     fingerprintPropertyRepository: FingerprintPropertyRepository,
     biometricSettingsRepository: BiometricSettingsRepository,
     keyEventInteractor: KeyEventInteractor,
@@ -77,9 +79,8 @@
                 emit(recentPowerButtonPressThresholdMs * -1L - 1L)
             }
 
-    val playSuccessHaptic: Flow<Boolean> =
-        repository.successHapticRequest
-            .filter { it }
+    val playSuccessHaptic: Flow<Unit> =
+        deviceEntryInteractor.enteringDeviceFromBiometricUnlock
             .sample(
                 combine(
                     powerButtonSideFpsEnrolled,
@@ -88,7 +89,7 @@
                     ::Triple
                 )
             )
-            .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+            .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
                 val sideFpsAllowsHaptic =
                     !powerButtonDown &&
                         systemClock.uptimeMillis() - lastPowerButtonWakeup >
@@ -96,38 +97,28 @@
                 val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
                 if (!allowHaptic) {
                     logger.d("Skip success haptic. Recent power button press or button is down.")
-                    handleSuccessHaptic() // immediately handle, don't vibrate
                 }
                 allowHaptic
             }
-    val playErrorHaptic: Flow<Boolean> =
-        repository.errorHapticRequest
-            .filter { it }
+            .map {} // map to Unit
+
+    private val playErrorHapticForBiometricFailure: Flow<Unit> =
+        merge(
+                deviceEntryFingerprintAuthInteractor.fingerprintFailure,
+                deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
+            )
+            .map {} // map to Unit
+    val playErrorHaptic: Flow<Unit> =
+        playErrorHapticForBiometricFailure
             .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
-            .map { (sideFpsEnrolled, powerButtonDown) ->
+            .filter { (sideFpsEnrolled, powerButtonDown) ->
                 val allowHaptic = !sideFpsEnrolled || !powerButtonDown
                 if (!allowHaptic) {
                     logger.d("Skip error haptic. Power button is down.")
-                    handleErrorHaptic() // immediately handle, don't vibrate
                 }
                 allowHaptic
             }
-
-    fun vibrateSuccess() {
-        repository.requestSuccessHaptic()
-    }
-
-    fun vibrateError() {
-        repository.requestErrorHaptic()
-    }
-
-    fun handleSuccessHaptic() {
-        repository.handleSuccessHaptic()
-    }
-
-    fun handleErrorHaptic() {
-        repository.handleErrorHaptic()
-    }
+            .map {} // map to Unit
 
     private val recentPowerButtonPressThresholdMs = 400L
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 4cddb9c..47be8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
@@ -30,6 +31,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
@@ -60,6 +62,9 @@
     trustRepository: TrustRepository,
     flags: SceneContainerFlags,
 ) {
+    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
+        repository.enteringDeviceFromBiometricUnlock
+
     /**
      * Whether the device is unlocked.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt
new file mode 100644
index 0000000..6d885b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.shared
+
+/** Models the biometrics that can be used to enter the device. */
+enum class DeviceEntryBiometricMode {
+    /** No biometrics can be used to enter the device from the lockscreen. */
+    NONE,
+    /** Only face can be used to enter the device from the lockscreen. */
+    FACE_ONLY,
+    /** Only fingerprint can be used to enter the device from the lockscreen. */
+    FINGERPRINT_ONLY,
+    /** Both face and fingerprint can be used to enter the device from the lockscreen. */
+    CO_EXPERIENCE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e8ceabf..5a763b1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -278,10 +278,6 @@
             "qs_user_detail_shortcut"
         )
 
-    // TODO(b/296357483): Tracking Bug
-    @JvmField
-    val QS_PIPELINE_NEW_TILES = unreleasedFlag("qs_pipeline_new_tiles")
-
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
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 8fe6309f..2ae5ce1 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
@@ -51,9 +51,21 @@
     companion object {
         private val wakeAndUnlockModes =
             setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+        private val dismissesKeyguardModes =
+            setOf(
+                WAKE_AND_UNLOCK,
+                WAKE_AND_UNLOCK_PULSING,
+                UNLOCK_COLLAPSING,
+                WAKE_AND_UNLOCK_FROM_DREAM,
+                DISMISS_BOUNCER
+            )
 
         fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean {
             return wakeAndUnlockModes.contains(model)
         }
+
+        fun dismissesKeyguard(model: BiometricUnlockModel): Boolean {
+            return dismissesKeyguardModes.contains(model)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index 3c143fe..cc385a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -49,7 +49,7 @@
 }
 
 /** Fingerprint authentication failed message. */
-object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+data object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
 
 /** Fingerprint authentication error message */
 data class ErrorFingerprintAuthenticationStatus(
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 4b4a19e..dcf4284 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
@@ -19,6 +19,7 @@
 
 import android.annotation.SuppressLint
 import android.content.res.ColorStateList
+import android.view.HapticFeedbackConstants
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -30,6 +31,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
@@ -51,6 +53,7 @@
         fgViewModel: DeviceEntryForegroundViewModel,
         bgViewModel: DeviceEntryBackgroundViewModel,
         falsingManager: FalsingManager,
+        vibratorHelper: VibratorHelper,
     ) {
         DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
         val longPressHandlingView = view.longPressHandlingView
@@ -62,6 +65,10 @@
                     if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
                         return
                     }
+                    vibratorHelper.performHapticFeedback(
+                        view,
+                        HapticFeedbackConstants.CONFIRM,
+                    )
                     viewModel.onLongPress()
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index e603ead..01a1ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -68,7 +68,6 @@
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -253,27 +252,21 @@
 
                     if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                         launch {
-                            deviceEntryHapticsInteractor.playSuccessHaptic
-                                .filter { it }
-                                .collect {
-                                    vibratorHelper.performHapticFeedback(
-                                        view,
-                                        HapticFeedbackConstants.CONFIRM,
-                                    )
-                                    deviceEntryHapticsInteractor.handleSuccessHaptic()
-                                }
+                            deviceEntryHapticsInteractor.playSuccessHaptic.collect {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    HapticFeedbackConstants.CONFIRM,
+                                )
+                            }
                         }
 
                         launch {
-                            deviceEntryHapticsInteractor.playErrorHaptic
-                                .filter { it }
-                                .collect {
-                                    vibratorHelper.performHapticFeedback(
-                                        view,
-                                        HapticFeedbackConstants.REJECT,
-                                    )
-                                    deviceEntryHapticsInteractor.handleErrorHaptic()
-                                }
+                            deviceEntryHapticsInteractor.playErrorHaptic.collect {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    HapticFeedbackConstants.REJECT,
+                                )
+                            }
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 77ab9f4..a693ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import dagger.Lazy
 import javax.inject.Inject
@@ -76,6 +77,7 @@
     @Application private val scope: CoroutineScope,
     private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
     private val tapGestureDetector: Lazy<TapGestureDetector>,
+    private val vibratorHelper: Lazy<VibratorHelper>,
 ) : KeyguardSection() {
     private val deviceEntryIconViewId = R.id.device_entry_icon_view
     private val alternateBouncerViewId = R.id.alternate_bouncer
@@ -114,6 +116,7 @@
                     deviceEntryForegroundViewModel.get(),
                     deviceEntryBackgroundViewModel.get(),
                     falsingManager.get(),
+                    vibratorHelper.get(),
                 )
             }
             constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
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 bd6aae8..f95713b 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
@@ -19,7 +19,6 @@
 import android.animation.FloatEvaluator
 import android.animation.IntEvaluator
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
@@ -56,7 +55,6 @@
     val shadeDependentFlows: ShadeDependentFlows,
     private val sceneContainerFlags: SceneContainerFlags,
     private val keyguardViewController: Lazy<KeyguardViewController>,
-    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
     private val deviceEntryInteractor: DeviceEntryInteractor,
 ) {
     private val intEvaluator = IntEvaluator()
@@ -182,8 +180,6 @@
         }
 
     fun onLongPress() {
-        deviceEntryHapticsInteractor.vibrateSuccess()
-
         // TODO (b/309804148): play auth ripple via an interactor
 
         if (sceneContainerFlags.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index dc55179f..d8bb3e6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -188,7 +188,7 @@
             LogBufferFactory factory,
             QSPipelineFlagsRepository flags
     ) {
-        if (flags.getPipelineTilesEnabled()) {
+        if (flags.getTilesEnabled()) {
             // we use
             return factory.create("QSLog", 450 /* maxSize */, false /* systrace */);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 1fab58e..828d6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -57,6 +57,8 @@
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.settings.SecureSettings;
 
+import dagger.Lazy;
+
 import org.jetbrains.annotations.NotNull;
 
 import java.io.PrintWriter;
@@ -73,8 +75,6 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import dagger.Lazy;
-
 /** Platform implementation of the quick settings tile host
  *
  * This class keeps track of the set of current tiles and is the in memory source of truth
@@ -151,7 +151,7 @@
 
         mShadeController = shadeController;
 
-        if (featureFlags.getPipelineTilesEnabled()) {
+        if (featureFlags.getTilesEnabled()) {
             mQsFactories.add(newQsTileFactoryProvider.get());
         }
         mQsFactories.add(defaultFactory);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 4bda730..5d28c8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -336,7 +336,7 @@
     private suspend fun createTile(spec: TileSpec): QSTile? {
         val tile =
             withContext(mainDispatcher) {
-                if (featureFlags.pipelineTilesEnabled) {
+                if (featureFlags.tilesEnabled) {
                     newQSTileFactory.get().createTile(spec.spec)
                 } else {
                     null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 5c7420c..935d072 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -2,24 +2,18 @@
 
 import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.flags.RefactorFlagUtils
 import javax.inject.Inject
 
 /** Encapsulate the different QS pipeline flags and their dependencies */
 @SysUISingleton
-class QSPipelineFlagsRepository
-@Inject
-constructor(
-    private val featureFlags: FeatureFlagsClassic,
-) {
+class QSPipelineFlagsRepository @Inject constructor() {
+
     val pipelineEnabled: Boolean
         get() = AconfigFlags.qsNewPipeline()
 
-    /** @see Flags.QS_PIPELINE_NEW_TILES */
-    val pipelineTilesEnabled: Boolean
-        get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_TILES)
+    val tilesEnabled: Boolean
+        get() = AconfigFlags.qsNewTiles()
 
     companion object Utils {
         fun assertInLegacyMode() =
@@ -27,5 +21,11 @@
                 AconfigFlags.qsNewPipeline(),
                 AconfigFlags.FLAG_QS_NEW_PIPELINE
             )
+
+        fun assertNewTilesInLegacyMode() =
+            RefactorFlagUtils.assertInLegacyMode(
+                AconfigFlags.qsNewTiles(),
+                AconfigFlags.FLAG_QS_NEW_TILES
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 27007bb..52e49f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.qs.QSFactory
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
 import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
@@ -44,6 +45,7 @@
 ) : QSFactory {
 
     init {
+        QSPipelineFlagsRepository.assertNewTilesInLegacyMode()
         for (viewModelTileSpec in tileMap.keys) {
             require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) {
                 "No config for $viewModelTileSpec"
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c885492..c43d20c 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -35,6 +35,8 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
 
+import java.util.Collections;
+
 /**
  * {@code FrameLayout} used to show and manipulate a {@link ToggleSeekBar}.
  *
@@ -48,6 +50,7 @@
     @Nullable
     private Drawable mProgressDrawable;
     private float mScale = 1f;
+    private final Rect mSystemGestureExclusionRect = new Rect();
 
     public BrightnessSliderView(Context context) {
         this(context, null);
@@ -176,6 +179,11 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         applySliderScale();
+        int horizontalMargin =
+                getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
+        mSystemGestureExclusionRect.set(-horizontalMargin, 0, right - left + horizontalMargin,
+                bottom - top);
+        setSystemGestureExclusionRects(Collections.singletonList(mSystemGestureExclusionRect));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index dd194eaa..8397caa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -794,13 +794,6 @@
 
     /** update Qs height state */
     public void setExpansionHeight(float height) {
-        // TODO(b/277909752): remove below log when bug is fixed
-        if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0
-                && mBarState == SHADE) {
-            Log.wtf(TAG,
-                    "setting QS height to 0 in split shade while shade is open(ing). "
-                            + "Value of isExpandImmediate() = " + isExpandImmediate());
-        }
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
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 4e77801..97fc35a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -47,7 +47,6 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -180,8 +179,6 @@
     private final SystemClock mSystemClock;
     private final boolean mOrderUnlockAndWake;
     private final Lazy<SelectedUserInteractor> mSelectedUserInteractor;
-    private final DeviceEntryHapticsInteractor mHapticsInteractor;
-
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
 
@@ -288,7 +285,6 @@
             ScreenOffAnimationController screenOffAnimationController,
             VibratorHelper vibrator,
             SystemClock systemClock,
-            DeviceEntryHapticsInteractor hapticsInteractor,
             Lazy<SelectedUserInteractor> selectedUserInteractor,
             BiometricUnlockInteractor biometricUnlockInteractor
     ) {
@@ -320,7 +316,6 @@
         mSystemClock = systemClock;
         mOrderUnlockAndWake = resources.getBoolean(
                 com.android.internal.R.bool.config_orderUnlockAndWake);
-        mHapticsInteractor = hapticsInteractor;
         mSelectedUserInteractor = selectedUserInteractor;
 
         dumpManager.registerDumpable(this);
@@ -442,7 +437,6 @@
         if (mode == MODE_WAKE_AND_UNLOCK
                 || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
                 || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
-            mHapticsInteractor.vibrateSuccess();
             onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
         }
         startWakeAndUnlock(mode);
@@ -726,14 +720,6 @@
             }
         }
 
-        // Suppress all face auth errors if fingerprint can be used to authenticate
-        if ((biometricSourceType == BiometricSourceType.FACE
-                && !mUpdateMonitor.isUnlockWithFingerprintPossible(
-                    mSelectedUserInteractor.get().getSelectedUserId()))
-                || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
-            mHapticsInteractor.vibrateError();
-        }
-
         cleanup();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
index 9b8e581..59bcf01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -18,157 +18,165 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.logging.BiometricUnlockLogger
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
-import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
+import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
-
-    private lateinit var repository: DeviceEntryHapticsRepository
-    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
-    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var keyEventRepository: FakeKeyEventRepository
-    private lateinit var powerRepository: FakePowerRepository
-    private lateinit var systemClock: FakeSystemClock
-    private lateinit var underTest: DeviceEntryHapticsInteractor
-
-    @Before
-    fun setUp() {
-        repository = DeviceEntryHapticsRepositoryImpl()
-        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
-        biometricSettingsRepository = FakeBiometricSettingsRepository()
-        keyEventRepository = FakeKeyEventRepository()
-        powerRepository = FakePowerRepository()
-        systemClock = FakeSystemClock()
-        underTest =
-            DeviceEntryHapticsInteractor(
-                repository = repository,
-                fingerprintPropertyRepository = fingerprintPropertyRepository,
-                biometricSettingsRepository = biometricSettingsRepository,
-                keyEventInteractor = KeyEventInteractor(keyEventRepository),
-                powerInteractor =
-                    PowerInteractor(
-                        powerRepository,
-                        mock(FalsingCollector::class.java),
-                        mock(ScreenOffAnimationController::class.java),
-                        mock(StatusBarStateController::class.java),
-                    ),
-                systemClock = systemClock,
-                logger = mock(BiometricUnlockLogger::class.java),
-            )
-    }
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.deviceEntryHapticsInteractor
 
     @Test
-    fun nonPowerButtonFPS_vibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isTrue()
-    }
+    fun nonPowerButtonFPS_vibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            runCurrent()
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNotNull()
+        }
 
     @Test
-    fun powerButtonFPS_vibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(false)
+    fun powerButtonFPS_vibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
 
-        // It's been 10 seconds since the last power button wakeup
-        setAwakeFromPowerButton()
-        runCurrent()
-        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+            // It's been 10 seconds since the last power button wakeup
+            setAwakeFromPowerButton()
+            runCurrent()
+            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
 
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isTrue()
-    }
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNotNull()
+        }
 
     @Test
-    fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+    fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
 
-        // It's been 10 seconds since the last power button wakeup
-        setAwakeFromPowerButton()
-        runCurrent()
-        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+            // It's been 10 seconds since the last power button wakeup
+            setAwakeFromPowerButton()
+            runCurrent()
+            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
 
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isFalse()
-    }
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNull()
+        }
 
     @Test
-    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest {
-        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(false)
+    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
 
-        // It's only been 50ms since the last power button wakeup
-        setAwakeFromPowerButton()
-        runCurrent()
-        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50)
+            // It's only been 50ms since the last power button wakeup
+            setAwakeFromPowerButton()
+            runCurrent()
+            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 50)
 
-        underTest.vibrateSuccess()
-        assertThat(playSuccessHaptic).isFalse()
-    }
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNull()
+        }
 
     @Test
-    fun nonPowerButtonFPS_vibrateError() = runTest {
-        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
-        underTest.vibrateError()
-        assertThat(playErrorHaptic).isTrue()
-    }
+    fun nonPowerButtonFPS_vibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNotNull()
+        }
 
     @Test
-    fun powerButtonFPS_vibrateError() = runTest {
-        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        underTest.vibrateError()
-        assertThat(playErrorHaptic).isTrue()
-    }
+    fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            coExEnrolledAndEnabled()
+            runCurrent()
+            faceFailure()
+            assertThat(playErrorHaptic).isNull()
+        }
 
     @Test
-    fun powerButtonFPS_powerDown_doNotVibrateError() = runTest {
-        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-        setPowerButtonFingerprintProperty()
-        setFingerprintEnrolled()
-        keyEventRepository.setPowerButtonDown(true)
-        underTest.vibrateError()
-        assertThat(playErrorHaptic).isFalse()
+    fun powerButtonFPS_vibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNotNull()
+        }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNull()
+        }
+
+    private suspend fun enterDeviceFromBiometricUnlock() {
+        kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock(
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
+    }
+
+    private fun fingerprintFailure() {
+        kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+            FailFingerprintAuthenticationStatus
+        )
+    }
+
+    private fun faceFailure() {
+        kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+            FailedFaceAuthenticationStatus()
+        )
     }
 
     private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
-        fingerprintPropertyRepository.setProperties(
+        kosmos.fingerprintPropertyRepository.setProperties(
             sensorId = 0,
             strength = SensorStrength.STRONG,
             sensorType = fingerprintSensorType,
@@ -181,15 +189,20 @@
     }
 
     private fun setFingerprintEnrolled() {
-        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+        kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
     }
 
     private fun setAwakeFromPowerButton() {
-        powerRepository.updateWakefulness(
+        kosmos.powerRepository.updateWakefulness(
             WakefulnessState.AWAKE,
             WakeSleepReason.POWER_BUTTON,
             WakeSleepReason.POWER_BUTTON,
             powerButtonLaunchGestureTriggered = false,
         )
     }
+
+    private fun coExEnrolledAndEnabled() {
+        setFingerprintEnrolled()
+        kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 67fba42..22569e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -91,6 +92,7 @@
                 TestScope().backgroundScope,
                 { mock(SwipeUpAnywhereGestureHandler::class.java) },
                 { mock(TapGestureDetector::class.java) },
+                { mock(VibratorHelper::class.java) },
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 5245b22..5e2423a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -18,6 +18,7 @@
 
 
 import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
+import static com.android.systemui.Flags.FLAG_QS_NEW_TILES;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -53,7 +54,6 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.nano.SystemUIProtoDump;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QSFactory;
@@ -79,6 +79,8 @@
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -93,8 +95,6 @@
 
 import javax.inject.Provider;
 
-import dagger.Lazy;
-
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class QSTileHostTest extends SysuiTestCase {
@@ -147,9 +147,8 @@
         mFeatureFlags = new FakeFeatureFlags();
 
         mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
-        // TODO(b/299909337): Add test checking the new factory is used when the flag is on
-        mFeatureFlags.set(Flags.QS_PIPELINE_NEW_TILES, false);
-        mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags);
+        mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES);
+        mQSPipelineFlagsRepository = new QSPipelineFlagsRepository();
 
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -704,7 +703,7 @@
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
             super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
-                    tunerService, autoTiles,  shadeController, qsLogger,
+                    tunerService, autoTiles, shadeController, qsLogger,
                     userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
index 2e63708..970cd17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -4,7 +4,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -13,9 +12,7 @@
 @RunWith(AndroidJUnit4::class)
 class QSPipelineFlagsRepositoryTest : SysuiTestCase() {
 
-    private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
-
-    private val underTest = QSPipelineFlagsRepository(fakeFeatureFlagsClassic)
+    private val underTest = QSPipelineFlagsRepository()
 
     @Test
     fun pipelineFlagDisabled() {
@@ -30,4 +27,18 @@
 
         assertThat(underTest.pipelineEnabled).isTrue()
     }
+
+    @Test
+    fun tilesFlagDisabled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_TILES)
+
+        assertThat(underTest.tilesEnabled).isFalse()
+    }
+
+    @Test
+    fun tilesFlagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_TILES)
+
+        assertThat(underTest.tilesEnabled).isTrue()
+    }
 }
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 051a4c1..6f65eb4 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
@@ -47,7 +47,6 @@
 import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -122,8 +121,6 @@
     @Mock
     private ViewRootImpl mViewRootImpl;
     @Mock
-    private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
-    @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private BiometricUnlockInteractor mBiometricUnlockInteractor;
@@ -160,7 +157,6 @@
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
                 mSystemClock,
-                mDeviceEntryHapticsInteractor,
                 () -> mSelectedUserInteractor,
                 mBiometricUnlockInteractor
         );
@@ -466,26 +462,6 @@
     }
 
     @Test
-    public void onFingerprintSuccess_requestSuccessHaptic() {
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN always vibrate the device
-        verify(mDeviceEntryHapticsInteractor).vibrateSuccess();
-    }
-
-    @Test
-    public void onFingerprintFail_requestErrorHaptic() {
-        // WHEN biometric fingerprint fails
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
-        // THEN always vibrate the device
-        verify(mDeviceEntryHapticsInteractor).vibrateError();
-    }
-
-    @Test
     public void onFingerprintDetect_showBouncer() {
         // WHEN fingerprint detect occurs
         mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index d97cb56..8ff04a63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.deviceentry.data
 
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryHapticsRepositoryModule
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
@@ -29,7 +28,6 @@
         [
             FakeBiometricSettingsRepositoryModule::class,
             FakeDeviceEntryRepositoryModule::class,
-            FakeDeviceEntryHapticsRepositoryModule::class,
             FakeDeviceEntryFaceAuthRepositoryModule::class,
             FakeDeviceEntryFingerprintAuthRepositoryModule::class,
             FakeFingerprintPropertyRepositoryModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt
deleted file mode 100644
index b29b67d..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 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.deviceentry.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Fake implementation of [DeviceEntryHapticsRepository] */
-@SysUISingleton
-class FakeDeviceEntryHapticsRepository @Inject constructor() : DeviceEntryHapticsRepository {
-    private var _successHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
-
-    private var _errorHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
-
-    override fun requestSuccessHaptic() {
-        _successHapticRequest.value = true
-    }
-
-    override fun handleSuccessHaptic() {
-        _successHapticRequest.value = false
-    }
-
-    override fun requestErrorHaptic() {
-        _errorHapticRequest.value = true
-    }
-
-    override fun handleErrorHaptic() {
-        _errorHapticRequest.value = false
-    }
-}
-
-@Module
-interface FakeDeviceEntryHapticsRepositoryModule {
-    @Binds fun bindFake(fake: FakeDeviceEntryHapticsRepository): DeviceEntryHapticsRepository
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index ba70d46..6436a38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,16 +16,24 @@
 package com.android.systemui.deviceentry.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [DeviceEntryRepository] */
 @SysUISingleton
 class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
+    private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> =
+        MutableSharedFlow()
+    override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
+        _enteringDeviceFromBiometricUnlock.asSharedFlow()
 
     private var isLockscreenEnabled = true
 
@@ -54,6 +62,10 @@
     fun setBypassEnabled(isBypassEnabled: Boolean) {
         _isBypassEnabled.value = isBypassEnabled
     }
+
+    suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) {
+        _enteringDeviceFromBiometricUnlock.emit(sourceType)
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt
new file mode 100644
index 0000000..1bd1056
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryBiometricAuthInteractor by
+    Kosmos.Fixture {
+        DeviceEntryBiometricAuthInteractor(
+            biometricSettingsRepository = biometricSettingsRepository,
+            deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
similarity index 61%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index 8bb07d9..d2dff78 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -14,10 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.deviceentry.data.repository
+@file:OptIn(ExperimentalCoroutinesApi::class)
 
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
 import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by
-    Kosmos.Fixture { fakeDeviceEntryHapticsRepository }
-val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() }
+val Kosmos.deviceEntryFaceAuthInteractor by
+    Kosmos.Fixture {
+        DeviceEntryFaceAuthInteractor(
+            repository = deviceEntryFaceAuthRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
new file mode 100644
index 0000000..66c6f86
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryFingerprintAuthInteractor by
+    Kosmos.Fixture {
+        DeviceEntryFingerprintAuthInteractor(
+            repository = deviceEntryFingerprintAuthRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index de6cacb..6bf527d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -20,7 +20,6 @@
 
 import com.android.keyguard.logging.biometricUnlockLogger
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryHapticsRepository
 import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.kosmos.Kosmos
@@ -31,7 +30,9 @@
 val Kosmos.deviceEntryHapticsInteractor by
     Kosmos.Fixture {
         DeviceEntryHapticsInteractor(
-            repository = fakeDeviceEntryHapticsRepository,
+            deviceEntryInteractor = deviceEntryInteractor,
+            deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+            deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
             fingerprintPropertyRepository = fingerprintPropertyRepository,
             biometricSettingsRepository = biometricSettingsRepository,
             keyEventInteractor = keyEventInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 299262b..6557bcf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.burnInInteractor
@@ -45,7 +44,6 @@
         shadeDependentFlows = shadeDependentFlows,
         sceneContainerFlags = sceneContainerFlags,
         keyguardViewController = { statusBarKeyguardViewManager },
-        deviceEntryHapticsInteractor = deviceEntryHapticsInteractor,
         deviceEntryInteractor = deviceEntryInteractor,
     )
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 45d7314..13c7924 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -104,6 +104,7 @@
 import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
 import com.android.server.companion.virtual.audio.VirtualAudioController;
 import com.android.server.companion.virtual.camera.VirtualCameraController;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -352,6 +353,14 @@
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
         }
         mBaseVirtualDisplayFlags = flags;
+
+        if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+            final String imeId = mParams.getInputMethodComponent().flattenToShortString();
+            Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device "
+                    + deviceId);
+            InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers(
+                    mDeviceId, imeId);
+        }
     }
 
     @VisibleForTesting
@@ -556,6 +565,12 @@
                 mCameraAccessController.stopObservingIfNeeded();
             }
 
+            // Clear any previously set custom IME components.
+            if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+                InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers(
+                        mDeviceId, null);
+            }
+
             mInputController.close();
             mSensorController.close();
         } finally {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9b78ed4..923728f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -859,6 +859,11 @@
         }
 
         @Override
+        public boolean isValidVirtualDeviceId(int deviceId) {
+            return mImpl.isValidVirtualDeviceId(deviceId);
+        }
+
+        @Override
         public @Nullable String getPersistentIdForDevice(int deviceId) {
             if (deviceId == Context.DEVICE_ID_DEFAULT) {
                 return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e54bf64..d077ebc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1501,7 +1501,7 @@
             } else {
                 status = addOp.deviceRoleAction(useCase, role, devices);
                 if (status == AudioSystem.SUCCESS) {
-                    rolesMap.put(key, devices);
+                    rolesMap.put(key, new ArrayList(devices));
                 }
             }
             return status;
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index 394e4af..e012d17 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -80,6 +80,7 @@
         sContext = context;
         sHandler = handler;
         sDisplayListener = new AudioDisplayListener();
+        sFoldStateListener = new FoldStateListener(sContext, RotationHelper::updateFoldState);
         sRotationCallback = rotationCallback;
         sFoldStateCallback = foldStateCallback;
         enable();
@@ -90,7 +91,6 @@
                 .registerDisplayListener(sDisplayListener, sHandler);
         updateOrientation();
 
-        sFoldStateListener = new FoldStateListener(sContext, folded -> updateFoldState(folded));
         sContext.getSystemService(DeviceStateManager.class)
                 .registerCallback(new HandlerExecutor(sHandler), sFoldStateListener);
     }
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index cecde55..823788f 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -21,6 +21,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.sensor.VirtualSensor;
+import android.content.Context;
 import android.os.LocaleList;
 import android.util.ArraySet;
 
@@ -149,6 +150,14 @@
     public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId);
 
     /**
+     * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
+     *
+     * <p>{@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
+     * device which is not a virtual device.</p>
+     */
+    public abstract boolean isValidVirtualDeviceId(int deviceId);
+
+    /**
      * Returns the ID of the device which owns the display with the given ID.
      *
      * <p>In case the virtual display ID is invalid or doesn't belong to a virtual device, then
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
index f57bf29..405c149 100644
--- a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -130,8 +130,8 @@
         }
 
         sendErrorNotification(createErrorNotification(
-                R.string.connected_display_cable_dont_support_displays_notification_title,
-                R.string.connected_display_cable_dont_support_displays_notification_content,
+                R.string.connected_display_unavailable_notification_title,
+                R.string.connected_display_unavailable_notification_content,
                 R.drawable.usb_cable_unknown_issue));
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index ffd714b..f526dbe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -120,6 +120,25 @@
             @UserIdInt int userId);
 
     /**
+     * Makes the input method associated with {@code imeId} the default input method for all users
+     * on displays that are owned by the virtual device with the given {@code deviceId}. If the
+     * input method associated with {@code imeId} is not available, there will be no IME on the
+     * relevant displays.
+     *
+     * <p>The caller of this method is responsible for resetting it to {@code null} after the
+     * virtual device is closed.</p>
+     *
+     * @param deviceId the device ID on which to use the given input method as default.
+     * @param imeId  the input method ID to be used as default on the given device. If {@code null},
+     *               then any existing input method association with that device will be removed.
+     * @throws IllegalArgumentException if a non-{@code null} input method ID is passed for a
+     *                                  device ID that already has a custom input method set or if
+     *                                  the device ID is not a valid virtual device.
+     */
+    public abstract void setVirtualDeviceInputMethodForAllUsers(
+            int deviceId, @Nullable String imeId);
+
+    /**
      * Registers a new {@link InputMethodListListener}.
      *
      * @param listener the listener to add
@@ -250,6 +269,11 @@
                 }
 
                 @Override
+                public void setVirtualDeviceInputMethodForAllUsers(
+                        int deviceId, @Nullable String imeId) {
+                }
+
+                @Override
                 public void registerInputMethodListListener(InputMethodListListener listener) {
                 }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 85c57de..09c388f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -169,6 +169,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.internal.view.IInputMethodManager;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
@@ -312,6 +313,9 @@
     // All known input methods.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     private final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
+    // Mapping from deviceId to the device-specific imeId for that device.
+    private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
+
     final InputMethodSubtypeSwitchingController mSwitchingController;
     final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
             new HardwareKeyboardShortcutController();
@@ -5620,6 +5624,23 @@
         }
 
         @Override
+        public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
+            // TODO(b/287269288): validate that id belongs to a valid virtual device instead.
+            Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+                    "DeviceId " + deviceId + " does not belong to a virtual device.");
+            synchronized (ImfLock.class) {
+                if (imeId == null) {
+                    mVirtualDeviceMethodMap.remove(deviceId);
+                } else if (mVirtualDeviceMethodMap.contains(deviceId)) {
+                    throw new IllegalArgumentException("Virtual device " + deviceId
+                            + " already has a custom input method component");
+                } else {
+                    mVirtualDeviceMethodMap.put(deviceId, imeId);
+                }
+            }
+        }
+
+        @Override
         public void registerInputMethodListListener(InputMethodListListener listener) {
             mInputMethodListListeners.addIfAbsent(listener);
         }
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f8b22c9..07dac54 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -443,6 +443,8 @@
         // indicates BAL would be blocked because only creator of the PI has the privilege to allow
         // BAL, the sender does not have the privilege to allow BAL.
         private boolean mOnlyCreatorAllows;
+        /** indicates that this verdict is based on the real calling UID and not the calling UID */
+        private boolean mBasedOnRealCaller;
 
         BalVerdict(@BalCode int balCode, boolean background, String message) {
             this.mBackground = background;
@@ -472,6 +474,15 @@
             return mOnlyCreatorAllows;
         }
 
+        private BalVerdict setBasedOnRealCaller() {
+            mBasedOnRealCaller = true;
+            return this;
+        }
+
+        private boolean isBasedOnRealCaller() {
+            return mBasedOnRealCaller;
+        }
+
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append(balCodeToString(mCode));
@@ -495,7 +506,15 @@
             return builder.toString();
         }
 
+        public @BalCode int getRawCode() {
+            return mCode;
+        }
+
         public @BalCode int getCode() {
+            if (mBasedOnRealCaller && mCode != BAL_BLOCK) {
+                // for compatibility always return BAL_ALLOW_PENDING_INTENT if based on real caller
+                return BAL_ALLOW_PENDING_INTENT;
+            }
             return mCode;
         }
     }
@@ -580,7 +599,8 @@
         // PendingIntents is null).
         BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
                 ? resultForCaller
-                : checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
+                : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
+                        .setBasedOnRealCaller();
         if (state.isPendingIntent()) {
             resultForCaller.setOnlyCreatorAllows(
                     resultForCaller.allows() && resultForRealCaller.blocks());
@@ -828,7 +848,7 @@
                 && ActivityManager.checkComponentPermission(
                 android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
                 state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
-            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+            return new BalVerdict(BAL_ALLOW_PERMISSION,
                     /*background*/ false,
                     "realCallingUid has BAL permission.");
         }
@@ -839,18 +859,18 @@
                 || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
         if (Flags.balImproveRealCallerVisibilityCheck()) {
             if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
-                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                         /*background*/ false, "realCallingUid has visible window");
             }
             if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
-                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                         /*background*/ false, "realCallingUid has non-app visible window");
             }
         } else {
             // don't abort if the realCallingUid has a visible window
             // TODO(b/171459802): We should check appSwitchAllowed also
             if (state.mRealCallingUidHasAnyVisibleWindow) {
-                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                         /*background*/ false,
                         "realCallingUid has visible (non-toast) window.");
             }
@@ -860,7 +880,7 @@
         // wasn't allowed to start an activity
         if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts()
                 && state.mIsRealCallingUidPersistentSystemProcess) {
-            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
                     /*background*/ false,
                     "realCallingUid is persistent system process AND intent "
                             + "sender forced to allow.");
@@ -868,7 +888,7 @@
         // don't abort if the realCallingUid is an associated companion app
         if (mService.isAssociatedCompanionApp(
                 UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
-            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ false,
                     "realCallingUid is a companion app.");
         }
@@ -1469,7 +1489,7 @@
                     intent != null ? intent.getComponent().flattenToShortString() : "";
             FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
                     activityName,
-                    code,
+                    BAL_ALLOW_PENDING_INTENT,
                     callingUid,
                     realCallingUid);
         }
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 9a32dc8..478524b 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -22,7 +22,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
-import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
@@ -49,6 +49,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -113,13 +114,17 @@
                     "process allowed by token");
         }
         // Allow if the caller is bound by a UID that's currently foreground.
-        if (isBoundByForegroundUid()) {
+        // But still respect the appSwitchState.
+        boolean allowBoundByForegroundUid =
+                Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid()
+                ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
+                : isBoundByForegroundUid();
+        if (allowBoundByForegroundUid) {
             return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
                     "process bound by foreground uid");
         }
         // Allow if the caller has an activity in any foreground task.
-        if (hasActivityInVisibleTask
-                && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
+        if (hasActivityInVisibleTask && appSwitchState != APP_SWITCH_DISALLOW) {
             return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
                     "process has activity in foreground task");
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 50376fe..a840973 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5502,6 +5502,10 @@
                 configureDisplayPolicy();
             }
 
+            if (!isDefaultDisplay) {
+                mDisplayRotation.updateRotationUnchecked(true);
+            }
+
             reconfigureDisplayLocked();
             onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
             mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
index 25d331f..23886a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -167,6 +167,11 @@
     }
 
     private void setStoppedState(int uid, String pkgName, boolean stopped) {
+        doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
+        sendPackageStoppedBroadcast(uid, pkgName, stopped);
+    }
+
+    private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) {
         Intent intent = new Intent(
                 stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED);
         intent.putExtra(Intent.EXTRA_UID, uid);
@@ -174,14 +179,6 @@
         mStoppedReceiver.onReceive(mContext, intent);
     }
 
-    private void setUidBias(int uid, int bias) {
-        int prevBias = mJobSchedulerService.getUidBias(uid);
-        doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
-        synchronized (mBackgroundJobsController.mLock) {
-            mBackgroundJobsController.onUidBiasChangedLocked(uid, prevBias, bias);
-        }
-    }
-
     private void trackJobs(JobStatus... jobs) {
         for (JobStatus job : jobs) {
             mJobStore.add(job);
@@ -208,6 +205,47 @@
     }
 
     @Test
+    public void testRestartedBroadcastWithoutStopping() {
+        mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+        // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+        JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+                createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+        // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+        JobStatus directJob2 = createJobStatus("testStopped",
+                ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+                createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+        // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+        JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+        // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+        JobStatus proxyJob2 = createJobStatus("testStopped",
+                ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+                createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+        trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+        sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+        assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob1.isUserBgRestricted());
+        assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob2.isUserBgRestricted());
+        assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob1.isUserBgRestricted());
+        assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob2.isUserBgRestricted());
+
+        sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+        assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob1.isUserBgRestricted());
+        assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(directJob2.isUserBgRestricted());
+        assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob1.isUserBgRestricted());
+        assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+        assertFalse(proxyJob2.isUserBgRestricted());
+    }
+
+    @Test
     public void testStopped_disabled() {
         mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
         // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9213601a..995d1f4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -444,18 +444,27 @@
     }
 
     @Test
+    public void isDeviceIdValid_invalidDeviceId_returnsFalse() {
+        assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse();
+        assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse();
+    }
+
+    @Test
     public void isDeviceIdValid_defaultDeviceId_returnsFalse() {
         assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse();
+        assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse();
     }
 
     @Test
     public void isDeviceIdValid_validVirtualDeviceId_returnsTrue() {
         assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue();
+        assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue();
     }
 
     @Test
     public void isDeviceIdValid_nonExistentDeviceId_returnsFalse() {
         assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse();
+        assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse();
     }
 
     @Test
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 0e1e0c8..3d2340c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -332,6 +332,7 @@
                         mUsageEventListeners.valueAt(i).onUsageEvent(userId, event);
                     }
                 }
+                return true;
             }
         }
         return false;
@@ -1973,6 +1974,8 @@
                 + ": " + Flags.userInteractionTypeApi());
         pw.println("    " + Flags.FLAG_USE_PARCELED_LIST
                 + ": " + Flags.useParceledList());
+        pw.println("    " + Flags.FLAG_FILTER_BASED_EVENT_QUERY_API
+                + ": " + Flags.filterBasedEventQueryApi());
 
         final int[] userIds;
         synchronized (mLock) {
@@ -2245,7 +2248,7 @@
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
-                    callingUid, userId);
+                    callingUid, UserHandle.getCallingUserId());
 
             final long token = Binder.clearCallingIdentity();
             try {
@@ -2384,6 +2387,7 @@
             if (!hasQueryPermission(callingPackage)) {
                 return null;
             }
+
             return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(),
                     query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
         }