Merge "Address remaining comment of ag/25829379" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 9e30843..70cda8b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -71,6 +71,8 @@
     ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
     ":android.webkit.flags-aconfig-java{.generated_srcjars}",
     ":android.provider.flags-aconfig-java{.generated_srcjars}",
+    ":android.chre.flags-aconfig-java{.generated_srcjars}",
+    ":android.speech.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -905,3 +907,23 @@
     aconfig_declarations: "android.provider.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// ContextHub
+java_aconfig_library {
+    name: "android.chre.flags-aconfig-java",
+    aconfig_declarations: "chre_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Speech
+aconfig_declarations {
+    name: "android.speech.flags-aconfig",
+    package: "android.speech.flags",
+    srcs: ["core/java/android/speech/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.speech.flags-aconfig-java",
+    aconfig_declarations: "android.speech.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index f3b2ebb..c84de51 100644
--- a/Android.bp
+++ b/Android.bp
@@ -175,9 +175,6 @@
         // and remove this line.
         "//frameworks/base/tools/hoststubgen:__subpackages__",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // AIDL files under these paths are mixture of public and private ones.
@@ -270,9 +267,6 @@
     ],
     sdk_version: "core_platform",
     installable: false,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -441,9 +435,6 @@
     ],
     sdk_version: "core_platform",
     installable: false,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -487,9 +478,6 @@
     ],
     compile_dex: false,
     headers_only: true,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -534,7 +522,7 @@
     },
     lint: {
         enabled: false,
-        baseline_filename: "lint-baseline.xml",
+
     },
 }
 
@@ -559,9 +547,6 @@
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -577,9 +562,6 @@
         "calendar-provider-compat-config",
         "contacts-provider-platform-compat-config",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 platform_compat_config {
@@ -634,9 +616,6 @@
         "rappor",
     ],
     dxflags: ["--core-library"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // utility classes statically linked into framework-wifi and dynamically linked
diff --git a/core/api/current.txt b/core/api/current.txt
index 7c34812..15fcf66 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4371,7 +4371,7 @@
     method public final android.media.session.MediaController getMediaController();
     method @NonNull public android.view.MenuInflater getMenuInflater();
     method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
-    method @Deprecated public final android.app.Activity getParent();
+    method public final android.app.Activity getParent();
     method @Nullable public android.content.Intent getParentActivityIntent();
     method public android.content.SharedPreferences getPreferences(int);
     method @Nullable public android.net.Uri getReferrer();
@@ -4389,7 +4389,7 @@
     method public void invalidateOptionsMenu();
     method public boolean isActivityTransitionRunning();
     method public boolean isChangingConfigurations();
-    method @Deprecated public final boolean isChild();
+    method public final boolean isChild();
     method public boolean isDestroyed();
     method public boolean isFinishing();
     method public boolean isImmersive();
@@ -41552,6 +41552,8 @@
     field public static final String EXTRA_LANGUAGE_MODEL = "android.speech.extra.LANGUAGE_MODEL";
     field public static final String EXTRA_LANGUAGE_PREFERENCE = "android.speech.extra.LANGUAGE_PREFERENCE";
     field public static final String EXTRA_LANGUAGE_SWITCH_ALLOWED_LANGUAGES = "android.speech.extra.LANGUAGE_SWITCH_ALLOWED_LANGUAGES";
+    field @FlaggedApi("android.speech.flags.multilang_extra_launch") public static final String EXTRA_LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS = "android.speech.extra.LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS";
+    field @FlaggedApi("android.speech.flags.multilang_extra_launch") public static final String EXTRA_LANGUAGE_SWITCH_MAX_SWITCHES = "android.speech.extra.LANGUAGE_SWITCH_MAX_SWITCHES";
     field public static final String EXTRA_MASK_OFFENSIVE_WORDS = "android.speech.extra.MASK_OFFENSIVE_WORDS";
     field public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
     field public static final String EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE = "android.speech.extra.ONLY_RETURN_LANGUAGE_PREFERENCE";
@@ -45343,7 +45345,7 @@
     method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
     method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
-    method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
+    method @FlaggedApi("com.android.internal.telephony.flags.enforce_subscription_user_filter") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
     method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
     method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c9e12c0..a532cdb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -862,10 +862,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
   }
 
-  @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager {
-    method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long);
-  }
-
   public class BroadcastOptions {
     method public void clearRequireCompatChange();
     method public int getPendingIntentBackgroundActivityStartMode();
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 1e1f155..5840f02 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -25,8 +25,6 @@
 import android.util.Property;
 import android.view.animation.AccelerateDecelerateInterpolator;
 
-import java.lang.ref.WeakReference;
-
 /**
  * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
  * The constructors of this class take parameters to define the target object that will be animated
@@ -73,11 +71,7 @@
 
     private static final boolean DBG = false;
 
-    /**
-     * A weak reference to the target object on which the property exists, set
-     * in the constructor. We'll cancel the animation if this goes away.
-     */
-    private WeakReference<Object> mTarget;
+    private Object mTarget;
 
     private String mPropertyName;
 
@@ -919,7 +913,7 @@
      */
     @Nullable
     public Object getTarget() {
-        return mTarget == null ? null : mTarget.get();
+        return mTarget;
     }
 
     @Override
@@ -929,7 +923,7 @@
             if (isStarted()) {
                 cancel();
             }
-            mTarget = target == null ? null : new WeakReference<Object>(target);
+            mTarget = target;
             // New target should cause re-initialization prior to starting
             mInitialized = false;
         }
@@ -977,13 +971,6 @@
     @Override
     void animateValue(float fraction) {
         final Object target = getTarget();
-        if (mTarget != null && target == null) {
-            // We lost the target reference, cancel and clean up. Note: we allow null target if the
-            /// target has never been set.
-            cancel();
-            return;
-        }
-
         super.animateValue(fraction);
         int numValues = mValues.length;
         for (int i = 0; i < numValues; ++i) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index be2582f..2103055 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1177,23 +1177,12 @@
         return mApplication;
     }
 
-    /**
-     * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
-     *
-     * @deprecated {@link ActivityGroup} is deprecated.
-     */
-    @Deprecated
+    /** Is this activity embedded inside of another activity? */
     public final boolean isChild() {
         return mParent != null;
     }
 
-    /**
-     * Returns the parent {@link Activity} if this is a child {@link Activity} of an
-     * {@link ActivityGroup}.
-     *
-     * @deprecated {@link ActivityGroup} is deprecated.
-     */
-    @Deprecated
+    /** Return the parent activity if this view is an embedded child. */
     public final Activity getParent() {
         return mParent;
     }
diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java
deleted file mode 100644
index f5b6878..0000000
--- a/core/java/android/app/BackgroundInstallControlManager.java
+++ /dev/null
@@ -1,101 +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 android.app;
-
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
-import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.content.pm.IBackgroundInstallControlService;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.ServiceManager;
-
-import java.util.List;
-
-/**
- * BackgroundInstallControlManager client allows apps to query apps installed in background.
- *
- * <p>Any applications that was installed without an accompanying installer UI activity paired
- * with recorded user interaction event is considered background installed. This is determined by
- * analysis of user-activity logs.
- *
- * <p>Warning: BackgroundInstallControl should not be considered a reliable or accurate
- * determination of background install application. Consumers can use this as a supplementary
- * signal, but must perform additional due diligence to confirm the install nature of the package.
- *
- * @hide
- */
-@FlaggedApi(Flags.FLAG_BIC_CLIENT)
-@SystemApi(client = PRIVILEGED_APPS)
-@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)
-public final class BackgroundInstallControlManager {
-
-    private static final String TAG = "BackgroundInstallControlManager";
-    private static IBackgroundInstallControlService sService;
-    private final Context mContext;
-
-    BackgroundInstallControlManager(Context context) {
-        mContext = context;
-    }
-
-    private static IBackgroundInstallControlService getService() {
-        if (sService == null) {
-            sService =
-                    IBackgroundInstallControlService.Stub.asInterface(
-                            ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
-        }
-        return sService;
-    }
-
-    /**
-     * Returns a full list of {@link PackageInfo} of apps currently installed that are considered
-     * installed in the background.
-     *
-     * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on
-     * background-installed applications.
-     * <p>
-     *
-     * @param flags - Flags will be used to call
-     * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages.
-     * @return A list of packages retrieved from {@link PackageManager} with non-background
-     * installed app filter applied.
-     *
-     * @hide
-     */
-    @FlaggedApi(Flags.FLAG_BIC_CLIENT)
-    @SystemApi
-    @RequiresPermission(QUERY_ALL_PACKAGES)
-    public @NonNull List<PackageInfo> getBackgroundInstalledPackages(
-            @PackageManager.PackageInfoFlagsBits long flags) {
-        try {
-            return getService()
-                    .getBackgroundInstalledPackages(flags, mContext.getUserId())
-                    .getList();
-        } catch (SecurityException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 390fa22..9cf732a 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1603,20 +1603,6 @@
                     }
                 });
 
-        // DO NOT do a flag check like this unless the flag is read-only.
-        // (because this code is executed during preload in zygote.)
-        // If the flag is mutable, the check should be inside CachedServiceFetcher.
-        if (Flags.bicClient()) {
-            registerService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE,
-                    BackgroundInstallControlManager.class,
-                    new CachedServiceFetcher<BackgroundInstallControlManager>() {
-                        @Override
-                        public BackgroundInstallControlManager createService(ContextImpl ctx) {
-                            return new BackgroundInstallControlManager(ctx);
-                        }
-                    });
-        }
-
         sInitializing = true;
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline
diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig
deleted file mode 100644
index 029b93a..0000000
--- a/core/java/android/app/background_install_control_manager.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "android.app"
-
-flag {
-     namespace: "background_install_control"
-     name: "bic_client"
-     description: "System API for background install control."
-     is_fixed_read_only: true
-     bug: "287507984"
-}
diff --git a/core/java/android/app/wearable/OWNERS b/core/java/android/app/wearable/OWNERS
index 073e2d7..497eaf0 100644
--- a/core/java/android/app/wearable/OWNERS
+++ b/core/java/android/app/wearable/OWNERS
@@ -1,3 +1,5 @@
 charliewang@google.com
+hackz@google.com
 oni@google.com
+tomchan@google.com
 volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/content/pm/IBackgroundInstallControlService.aidl b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
index 4bc8fe1..c8e7cae 100644
--- a/core/java/android/content/pm/IBackgroundInstallControlService.aidl
+++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
@@ -16,20 +16,11 @@
 
 package android.content.pm;
 
-
 import android.content.pm.ParceledListSlice;
-import android.os.IRemoteCallback;
 
 /**
  * {@hide}
  */
 interface IBackgroundInstallControlService {
-    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)")
     ParceledListSlice getBackgroundInstalledPackages(long flags, int userId);
-
-    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.QUERY_ALL_PACKAGES, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
-    void registerBackgroundInstallCallback(IRemoteCallback callback);
-
-    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.QUERY_ALL_PACKAGES, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
-    void unregisterBackgroundInstallCallback(IRemoteCallback callback);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index c727a60..561db9c 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -102,6 +102,24 @@
     public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY =
             "vcn_network_selection_wifi_exit_rssi_threshold";
 
+    /**
+     * Key for the interval to poll IpSecTransformState for packet loss monitoring
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY =
+            "vcn_network_selection_poll_ipsec_state_interval_seconds";
+
+    /**
+     * Key for the threshold of IPSec packet loss rate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
+            "vcn_network_selection_ipsec_packet_loss_percent_threshold";
+
     // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
 
     /**
@@ -148,6 +166,8 @@
             new String[] {
                 VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
                 VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
                 VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
                 VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
                 VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 67a1906..7afd721 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -12,4 +12,11 @@
     namespace: "vcn"
     description: "Feature flag for adjustable safe mode timeout"
     bug: "317406085"
+}
+
+flag{
+    name: "network_metric_monitor"
+    namespace: "vcn"
+    description: "Feature flag for enabling network metric monitor"
+    bug: "282996138"
 }
\ No newline at end of file
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 7d9c0a3..2d657c2 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -445,6 +445,19 @@
     }
 
     /**
+     * Retrieves the current {@link android.app.Activity} associated with the dream.
+     * This method behaves similarly to calling {@link android.app.Activity#getActivity()}.
+     *
+     * @return The current activity, or null if the dream is not associated with an activity
+     * or not started.
+     *
+     * @hide
+     */
+    public Activity getActivity() {
+        return mActivity;
+    }
+
+    /**
      * Inflates a layout resource and set it to be the content view for this Dream.
      * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
      *
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 92c516c..7658af5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -42,6 +42,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1056,7 +1057,7 @@
             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
                     .getActiveNotificationsFromListener(mWrapper, keys, trim);
             return cleanUpNotificationList(parceledList);
-        } catch (android.os.RemoteException ex) {
+        } catch (android.os.RemoteException | BadParcelableException ex) {
             Log.v(TAG, "Unable to contact notification manager", ex);
         }
         return null;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 45a0c20..54248be 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -64,6 +64,7 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -233,6 +234,7 @@
 
     private static final String MANUAL_TAG = "manual";
     private static final String AUTOMATIC_TAG = "automatic";
+    private static final String AUTOMATIC_DELETED_TAG = "deleted";
 
     private static final String RULE_ATT_ID = "ruleId";
     private static final String RULE_ATT_ENABLED = "enabled";
@@ -251,6 +253,7 @@
     private static final String RULE_ATT_USER_MODIFIED_FIELDS = "userModifiedFields";
     private static final String RULE_ATT_ICON = "rule_icon";
     private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc";
+    private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant";
 
     private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale";
     private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY =
@@ -292,6 +295,10 @@
     @UnsupportedAppUsage
     public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
 
+    // Note: Map is *pkg|conditionId* (see deletedRuleKey()) -> ZenRule,
+    // unlike automaticRules (which is id -> rule).
+    public final ArrayMap<String, ZenRule> deletedRules = new ArrayMap<>();
+
     @UnsupportedAppUsage
     public ZenModeConfig() {
     }
@@ -306,15 +313,9 @@
         allowMessagesFrom = source.readInt();
         user = source.readInt();
         manualRule = source.readParcelable(null, ZenRule.class);
-        final int len = source.readInt();
-        if (len > 0) {
-            final String[] ids = new String[len];
-            final ZenRule[] rules = new ZenRule[len];
-            source.readStringArray(ids);
-            source.readTypedArray(rules, ZenRule.CREATOR);
-            for (int i = 0; i < len; i++) {
-                automaticRules.put(ids[i], rules[i]);
-            }
+        readRulesFromParcel(automaticRules, source);
+        if (Flags.modesApi()) {
+            readRulesFromParcel(deletedRules, source);
         }
         allowAlarms = source.readInt() == 1;
         allowMedia = source.readInt() == 1;
@@ -328,6 +329,19 @@
         }
     }
 
+    private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) {
+        final int len = source.readInt();
+        if (len > 0) {
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            source.readStringArray(ids);
+            source.readTypedArray(rules, ZenRule.CREATOR);
+            for (int i = 0; i < len; i++) {
+                ruleMap.put(ids[i], rules[i]);
+            }
+        }
+    }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(allowCalls ? 1 : 0);
@@ -339,19 +353,9 @@
         dest.writeInt(allowMessagesFrom);
         dest.writeInt(user);
         dest.writeParcelable(manualRule, 0);
-        if (!automaticRules.isEmpty()) {
-            final int len = automaticRules.size();
-            final String[] ids = new String[len];
-            final ZenRule[] rules = new ZenRule[len];
-            for (int i = 0; i < len; i++) {
-                ids[i] = automaticRules.keyAt(i);
-                rules[i] = automaticRules.valueAt(i);
-            }
-            dest.writeInt(len);
-            dest.writeStringArray(ids);
-            dest.writeTypedArray(rules, 0);
-        } else {
-            dest.writeInt(0);
+        writeRulesToParcel(automaticRules, dest);
+        if (Flags.modesApi()) {
+            writeRulesToParcel(deletedRules, dest);
         }
         dest.writeInt(allowAlarms ? 1 : 0);
         dest.writeInt(allowMedia ? 1 : 0);
@@ -365,6 +369,23 @@
         }
     }
 
+    private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) {
+        if (!ruleMap.isEmpty()) {
+            final int len = ruleMap.size();
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            for (int i = 0; i < len; i++) {
+                ids[i] = ruleMap.keyAt(i);
+                rules[i] = ruleMap.valueAt(i);
+            }
+            dest.writeInt(len);
+            dest.writeStringArray(ids);
+            dest.writeTypedArray(rules, 0);
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
@@ -389,23 +410,26 @@
         } else {
             sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd);
         }
-        return sb.append(",\nautomaticRules=").append(rulesToString())
-                .append(",\nmanualRule=").append(manualRule)
-                .append(']').toString();
+        sb.append(",\nautomaticRules=").append(rulesToString(automaticRules))
+                .append(",\nmanualRule=").append(manualRule);
+        if (Flags.modesApi()) {
+            sb.append(",\ndeletedRules=").append(rulesToString(deletedRules));
+        }
+        return sb.append(']').toString();
     }
 
-    private String rulesToString() {
-        if (automaticRules.isEmpty()) {
+    private static String rulesToString(ArrayMap<String, ZenRule> ruleList) {
+        if (ruleList.isEmpty()) {
             return "{}";
         }
 
-        StringBuilder buffer = new StringBuilder(automaticRules.size() * 28);
+        StringBuilder buffer = new StringBuilder(ruleList.size() * 28);
         buffer.append("{\n");
-        for (int i = 0; i < automaticRules.size(); i++) {
+        for (int i = 0; i < ruleList.size(); i++) {
             if (i > 0) {
                 buffer.append(",\n");
             }
-            Object value = automaticRules.valueAt(i);
+            Object value = ruleList.valueAt(i);
             buffer.append(value);
         }
         buffer.append('}');
@@ -487,7 +511,9 @@
                 && other.allowConversations == allowConversations
                 && other.allowConversationsFrom == allowConversationsFrom;
         if (Flags.modesApi()) {
-            return eq && other.allowPriorityChannels == allowPriorityChannels;
+            return eq
+                    && Objects.equals(other.deletedRules, deletedRules)
+                    && other.allowPriorityChannels == allowPriorityChannels;
         }
         return eq;
     }
@@ -644,12 +670,20 @@
                             DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
                 } else if (MANUAL_TAG.equals(tag)) {
                     rt.manualRule = readRuleXml(parser);
-                } else if (AUTOMATIC_TAG.equals(tag)) {
+                } else if (AUTOMATIC_TAG.equals(tag)
+                        || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
                     final String id = parser.getAttributeValue(null, RULE_ATT_ID);
                     final ZenRule automaticRule = readRuleXml(parser);
                     if (id != null && automaticRule != null) {
                         automaticRule.id = id;
-                        rt.automaticRules.put(id, automaticRule);
+                        if (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag)) {
+                            String deletedRuleKey = deletedRuleKey(automaticRule);
+                            if (deletedRuleKey != null) {
+                                rt.deletedRules.put(deletedRuleKey, automaticRule);
+                            }
+                        } else if (AUTOMATIC_TAG.equals(tag)) {
+                            rt.automaticRules.put(id, automaticRule);
+                        }
                     }
                 } else if (STATE_TAG.equals(tag)) {
                     rt.areChannelsBypassingDnd = safeBoolean(parser,
@@ -660,13 +694,24 @@
         throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
 
+    /** Generates the map key used for a {@link ZenRule} in {@link #deletedRules}. */
+    @Nullable
+    public static String deletedRuleKey(ZenRule rule) {
+        if (rule.pkg != null && rule.conditionId != null) {
+            return rule.pkg + "|" + rule.conditionId.toString();
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Writes XML of current ZenModeConfig
      * @param out serializer
      * @param version uses XML_VERSION if version is null
      * @throws IOException
      */
-    public void writeXml(TypedXmlSerializer out, Integer version) throws IOException {
+    public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup)
+            throws IOException {
         out.startTag(null, ZEN_TAG);
         out.attribute(null, ZEN_ATT_VERSION, version == null
                 ? Integer.toString(XML_VERSION) : Integer.toString(version));
@@ -707,6 +752,15 @@
             writeRuleXml(automaticRule, out);
             out.endTag(null, AUTOMATIC_TAG);
         }
+        if (Flags.modesApi() && !forBackup) {
+            for (int i = 0; i < deletedRules.size(); i++) {
+                final ZenRule deletedRule = deletedRules.valueAt(i);
+                out.startTag(null, AUTOMATIC_DELETED_TAG);
+                out.attribute(null, RULE_ATT_ID, deletedRule.id);
+                writeRuleXml(deletedRule, out);
+                out.endTag(null, AUTOMATIC_DELETED_TAG);
+            }
+        }
 
         out.startTag(null, STATE_TAG);
         out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd);
@@ -752,6 +806,11 @@
             rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
             rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
             rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
+            Long deletionInstant = tryParseLong(
+                    parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null);
+            if (deletionInstant != null) {
+                rt.deletionInstant = Instant.ofEpochMilli(deletionInstant);
+            }
         }
         return rt;
     }
@@ -799,6 +858,10 @@
             }
             out.attributeInt(null, RULE_ATT_TYPE, rule.type);
             out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
+            if (rule.deletionInstant != null) {
+                out.attributeLong(null, RULE_ATT_DELETION_INSTANT,
+                        rule.deletionInstant.toEpochMilli());
+            }
         }
     }
 
@@ -1998,6 +2061,7 @@
         public String iconResName;
         public boolean allowManualInvocation;
         public int userModifiedFields;
+        @Nullable public Instant deletionInstant; // Only set on deleted rules.
 
         public ZenRule() { }
 
@@ -2031,6 +2095,9 @@
                 triggerDescription = source.readString();
                 type = source.readInt();
                 userModifiedFields = source.readInt();
+                if (source.readInt() == 1) {
+                    deletionInstant = Instant.ofEpochMilli(source.readLong());
+                }
             }
         }
 
@@ -2091,6 +2158,12 @@
                 dest.writeString(triggerDescription);
                 dest.writeInt(type);
                 dest.writeInt(userModifiedFields);
+                if (deletionInstant != null) {
+                    dest.writeInt(1);
+                    dest.writeLong(deletionInstant.toEpochMilli());
+                } else {
+                    dest.writeInt(0);
+                }
             }
         }
 
@@ -2121,6 +2194,9 @@
                         .append(",triggerDescription=").append(triggerDescription)
                         .append(",type=").append(type)
                         .append(",userModifiedFields=").append(userModifiedFields);
+                if (deletionInstant != null) {
+                    sb.append(",deletionInstant=").append(deletionInstant);
+                }
             }
 
             return sb.append(']').toString();
@@ -2180,7 +2256,8 @@
                         && Objects.equals(other.iconResName, iconResName)
                         && Objects.equals(other.triggerDescription, triggerDescription)
                         && other.type == type
-                        && other.userModifiedFields == userModifiedFields;
+                        && other.userModifiedFields == userModifiedFields
+                        && Objects.equals(other.deletionInstant, deletionInstant);
             }
 
             return finalEquals;
@@ -2192,7 +2269,7 @@
                 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                         component, configurationActivity, pkg, id, enabler, zenPolicy,
                         zenDeviceEffects, modified, allowManualInvocation, iconResName,
-                        triggerDescription, type, userModifiedFields);
+                        triggerDescription, type, userModifiedFields, deletionInstant);
             }
             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                     component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index 8902368..91ef11c 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -30,6 +30,11 @@
 /**
  * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
  * subcomponents (automatic and manual ZenRules).
+ *
+ * <p>Note that this class is intended to detect <em>meaningful</em> differences, so objects that
+ * are not identical (as per their {@code equals()} implementation) can still produce an empty diff
+ * if only "metadata" fields are updated.
+ *
  * @hide
  */
 public class ZenModeDiff {
@@ -467,7 +472,6 @@
         public static final String FIELD_ICON_RES = "iconResName";
         public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
         public static final String FIELD_TYPE = "type";
-        public static final String FIELD_USER_MODIFIED_FIELDS = "userModifiedFields";
         // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
 
         // Special field to track whether this rule became active or inactive
@@ -563,10 +567,6 @@
                 if (!Objects.equals(from.iconResName, to.iconResName)) {
                     addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
                 }
-                if (from.userModifiedFields != to.userModifiedFields) {
-                    addField(FIELD_USER_MODIFIED_FIELDS,
-                            new FieldDiff<>(from.userModifiedFields, to.userModifiedFields));
-                }
             }
         }
 
diff --git a/core/java/android/service/wearable/OWNERS b/core/java/android/service/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/core/java/android/service/wearable/OWNERS
+++ b/core/java/android/service/wearable/OWNERS
@@ -1,3 +1 @@
-charliewang@google.com
-oni@google.com
-volnov@google.com
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 118d028..1ca7ac7 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -16,6 +16,9 @@
 
 package android.speech;
 
+import static android.speech.flags.Flags.FLAG_MULTILANG_EXTRA_LAUNCH;
+
+import android.annotation.FlaggedApi;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -653,4 +656,30 @@
      */
     public static final String EXTRA_LANGUAGE_SWITCH_ALLOWED_LANGUAGES =
             "android.speech.extra.LANGUAGE_SWITCH_ALLOWED_LANGUAGES";
+
+    /**
+     * Optional integer to use for {@link #EXTRA_ENABLE_LANGUAGE_SWITCH}. If set, the language
+     * switch will be deactivated when LANGUAGE_SWITCH_MAX_SWITCHES reached.
+     *
+     * <p> Depending on the recognizer implementation, this flag may have no effect.
+     *
+     * @see #EXTRA_ENABLE_LANGUAGE_SWITCH
+     */
+    @FlaggedApi(FLAG_MULTILANG_EXTRA_LAUNCH)
+    public static final String EXTRA_LANGUAGE_SWITCH_MAX_SWITCHES =
+            "android.speech.extra.LANGUAGE_SWITCH_MAX_SWITCHES";
+
+    /**
+     * Optional integer to use for {@link #EXTRA_ENABLE_LANGUAGE_SWITCH}. If set, the language
+     * switch will only be activated for this value of ms of audio since the START_OF_SPEECH. This
+     * could provide a more stable recognition result when the language switch is only required in
+     * the beginning of the session.
+     *
+     * <p> Depending on the recognizer implementation, this flag may have no effect.
+     *
+     * @see #EXTRA_ENABLE_LANGUAGE_SWITCH
+     */
+    @FlaggedApi(FLAG_MULTILANG_EXTRA_LAUNCH)
+    public static final String EXTRA_LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS =
+            "android.speech.extra.LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS";
 }
diff --git a/core/java/android/speech/flags/speech_flags.aconfig b/core/java/android/speech/flags/speech_flags.aconfig
new file mode 100644
index 0000000..fd80127
--- /dev/null
+++ b/core/java/android/speech/flags/speech_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.speech.flags"
+
+flag {
+    name: "multilang_extra_launch"
+    namespace: "machine_learning"
+    description: "Feature flag for adding new extra for multi-lang feature"
+    bug: "312489931"
+}
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index a74b06a..9f9b7b4 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,6 +1,13 @@
 package: "android.view.flags"
 
 flag {
+     name: "enable_surface_native_alloc_registration"
+     namespace: "toolkit"
+     description: "Feature flag for registering surfaces with the VM for faster cleanup"
+     bug: "306193257"
+}
+
+flag {
     name: "enable_use_measure_cache_during_force_layout"
     namespace: "toolkit"
     description: "Enables using the measure cache during a view force layout from the second "
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index bdaad2b..473b814 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -337,7 +338,14 @@
                         "SplashScreenView");
                 ImageView imageView = new ImageView(viewContext);
                 imageView.setBackground(mIconDrawable);
-                viewHost.setView(imageView, mIconSize, mIconSize);
+                final int windowFlag = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+                final WindowManager.LayoutParams lp =
+                        new WindowManager.LayoutParams(mIconSize, mIconSize,
+                                WindowManager.LayoutParams.TYPE_APPLICATION, windowFlag,
+                                PixelFormat.TRANSPARENT);
+                viewHost.setView(imageView, lp);
                 SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
                 surfaceView.setChildSurfacePackage(surfacePackage);
                 view.mSurfacePackage = surfacePackage;
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index db391f7..a854e36 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -18,6 +18,7 @@
 per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
 per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
 per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
+per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
 
 # Biometrics
 jaggies@google.com
@@ -31,5 +32,3 @@
 
 # Accessibility
 pweaver@google.com
-hongmingjin@google.com
-cbrower@google.com
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index af30532..31acd9a 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -90,4 +90,7 @@
          {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER}
          devices. -->
     <bool name="config_viewRotaryEncoderHapticScrollFedbackEnabled">true</bool>
+
+    <!-- If this is true, allow wake from theater mode from motion. -->
+    <bool name="config_allowTheaterModeWakeFromMotion">true</bool>
 </resources>
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 5ad144d..45540e0 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -175,3 +175,74 @@
     plugins: ["dagger2-compiler"],
     use_resource_processor: true,
 }
+
+android_app {
+    name: "WindowManagerShellRobolectric",
+    platform_apis: true,
+    static_libs: [
+        "WindowManager-Shell",
+    ],
+    manifest: "multivalentTests/AndroidManifestRobolectric.xml",
+    use_resource_processor: true,
+}
+
+android_robolectric_test {
+    name: "WMShellRobolectricTests",
+    instrumentation_for: "WindowManagerShellRobolectric",
+    upstream: true,
+    java_resource_dirs: [
+        "multivalentTests/robolectric/config",
+    ],
+    srcs: [
+        "multivalentTests/src/**/*.kt",
+    ],
+    static_libs: [
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-robolectric-prebuilt",
+        "mockito-kotlin2",
+        "truth",
+    ],
+}
+
+android_test {
+    name: "WMShellMultivalentTestsOnDevice",
+    srcs: [
+        "multivalentTests/src/**/*.kt",
+    ],
+    static_libs: [
+        "WindowManager-Shell",
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "mockito-kotlin2",
+        "mockito-target-extended-minus-junit4",
+        "truth",
+        "platform-test-annotations",
+        "platform-test-rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    optimize: {
+        enabled: false,
+    },
+    test_suites: ["device-tests"],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--extra-packages",
+        "com.android.wm.shell",
+    ],
+    manifest: "multivalentTests/AndroidManifest.xml",
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
new file mode 100644
index 0000000..f8f8338
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.multivalenttests">
+
+    <application android:debuggable="true" android:supportsRtl="true" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Multivalent tests for WindowManager-Shell"
+        android:targetPackage="com.android.wm.shell.multivalenttests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
new file mode 100644
index 0000000..ffcd7d4
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.multivalenttests">
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
new file mode 100644
index 0000000..36fe8ec
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Tests for WindowManagerShellLib">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="WMShellMultivalentTestsOnDevice.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="WMShellMultivalentTestsOnDevice" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.wm.shell.multivalenttests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..7a0527c
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
@@ -0,0 +1,2 @@
+sdk=NEWEST_SDK
+
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
new file mode 100644
index 0000000..ea7c6ed
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -0,0 +1,481 @@
+/*
+ * 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.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.graphics.Insets
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests operations and the resulting state managed by [BubblePositioner]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubblePositionerTest {
+
+    private lateinit var positioner: BubblePositioner
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private val defaultDeviceConfig =
+        DeviceConfig(
+            windowBounds = Rect(0, 0, 1000, 2000),
+            isLargeScreen = false,
+            isSmallTablet = false,
+            isLandscape = false,
+            isRtl = false,
+            insets = Insets.of(0, 0, 0, 0)
+        )
+
+    @Before
+    fun setUp() {
+        val windowManager = context.getSystemService(WindowManager::class.java)
+        positioner = BubblePositioner(context, windowManager)
+    }
+
+    @Test
+    fun testUpdate() {
+        val insets = Insets.of(10, 20, 5, 15)
+        val screenBounds = Rect(0, 0, 1000, 1200)
+        val availableRect = Rect(screenBounds)
+        availableRect.inset(insets)
+        positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds))
+        assertThat(positioner.availableRect).isEqualTo(availableRect)
+        assertThat(positioner.isLandscape).isFalse()
+        assertThat(positioner.isLargeScreen).isFalse()
+        assertThat(positioner.insets).isEqualTo(insets)
+    }
+
+    @Test
+    fun testShowBubblesVertically_phonePortrait() {
+        positioner.update(defaultDeviceConfig)
+        assertThat(positioner.showBubblesVertically()).isFalse()
+    }
+
+    @Test
+    fun testShowBubblesVertically_phoneLandscape() {
+        positioner.update(defaultDeviceConfig.copy(isLandscape = true))
+        assertThat(positioner.isLandscape).isTrue()
+        assertThat(positioner.showBubblesVertically()).isTrue()
+    }
+
+    @Test
+    fun testShowBubblesVertically_tablet() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+        assertThat(positioner.showBubblesVertically()).isTrue()
+    }
+
+    /** If a resting position hasn't been set, calling it will return the default position. */
+    @Test
+    fun testGetRestingPosition_returnsDefaultPosition() {
+        positioner.update(defaultDeviceConfig)
+        val restingPosition = positioner.getRestingPosition()
+        val defaultPosition = positioner.defaultStartPosition
+        assertThat(restingPosition).isEqualTo(defaultPosition)
+    }
+
+    /** If a resting position has been set, it'll return that instead of the default position. */
+    @Test
+    fun testGetRestingPosition_returnsRestingPosition() {
+        positioner.update(defaultDeviceConfig)
+        val restingPosition = PointF(100f, 100f)
+        positioner.restingPosition = restingPosition
+        assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition)
+    }
+
+    /** Test that the default resting position on phone is in upper left. */
+    @Test
+    fun testGetRestingPosition_bubble_onPhone() {
+        positioner.update(defaultDeviceConfig)
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testGetRestingPosition_bubble_onPhone_RTL() {
+        positioner.update(defaultDeviceConfig.copy(isRtl = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    /** Test that the default resting position on tablet is middle left. */
+    @Test
+    fun testGetRestingPosition_chatBubble_onTablet() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testGetRestingPosition_chatBubble_onTablet_RTL() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    /** Test that the default resting position on tablet is middle right. */
+    @Test
+    fun testGetDefaultPosition_appBubble_onTablet() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+        assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
+        assertThat(startPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testGetRestingPosition_appBubble_onTablet_RTL() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+        assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
+        assertThat(startPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testHasUserModifiedDefaultPosition_false() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+        positioner.restingPosition = positioner.defaultStartPosition
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+    }
+
+    @Test
+    fun testHasUserModifiedDefaultPosition_true() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+        positioner.restingPosition = PointF(0f, 100f)
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue()
+    }
+
+    @Test
+    fun testGetExpandedViewHeight_max() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
+    }
+
+    @Test
+    fun testGetExpandedViewHeight_customHeight_valid() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+        val minHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+        val bubble =
+            Bubble(
+                "key",
+                ShortcutInfo.Builder(context, "id").build(),
+                minHeight + 100 /* desiredHeight */,
+                0 /* desiredHeightResId */,
+                "title",
+                0 /* taskId */,
+                null /* locus */,
+                true /* isDismissable */,
+                directExecutor()) {}
+
+        // Ensure the height is the same as the desired value
+        assertThat(positioner.getExpandedViewHeight(bubble))
+            .isEqualTo(bubble.getDesiredHeight(context))
+    }
+
+    @Test
+    fun testGetExpandedViewHeight_customHeight_tooSmall() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val bubble =
+            Bubble(
+                "key",
+                ShortcutInfo.Builder(context, "id").build(),
+                10 /* desiredHeight */,
+                0 /* desiredHeightResId */,
+                "title",
+                0 /* taskId */,
+                null /* locus */,
+                true /* isDismissable */,
+                directExecutor()) {}
+
+        // Ensure the height is the same as the desired value
+        val minHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+        assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight)
+    }
+
+    @Test
+    fun testGetMaxExpandedViewHeight_onLargeTablet() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val manageButtonHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+        val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+        val expandedViewPadding =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+        val expectedHeight =
+            1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2
+        assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */))
+            .isEqualTo(expectedHeight)
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_largeScreen_true() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isTrue()
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_largeScreen_landscape_false() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isFalse()
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_smallTablet_false() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                isSmallTablet = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isFalse()
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_phone_false() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isFalse()
+    }
+
+    @Test
+    fun testExpandedViewY_phoneLandscape() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height so it'll always be top aligned
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_phonePortrait() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // Always top aligned in phone portrait
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_smallTabletLandscape() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isSmallTablet = true,
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height which is always top aligned on small tablets
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_smallTabletPortrait() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isSmallTablet = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height which is always top aligned on small tablets
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_largeScreenLandscape() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height which is always top aligned on landscape, large tablet
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_largeScreenPortrait() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        val manageButtonHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+        val manageButtonPlusMargin =
+            manageButtonHeight +
+                2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin)
+        val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+
+        val expectedExpandedViewY =
+            positioner.availableRect.bottom -
+                manageButtonPlusMargin -
+                positioner.getExpandedViewHeightForLargeScreen() -
+                pointerWidth
+
+        // Bubbles are bottom aligned on portrait, large tablet
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(expectedExpandedViewY)
+    }
+
+    private val defaultYPosition: Float
+        /**
+         * Calculates the Y position bubbles should be placed based on the config. Based on the
+         * calculations in [BubblePositioner.getDefaultStartPosition] and
+         * [BubbleStackView.RelativeStackPosition].
+         */
+        get() {
+            val isTablet = positioner.isLargeScreen
+
+            // On tablet the position is centered, on phone it is an offset from the top.
+            val desiredY =
+                if (isTablet) {
+                    positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f
+                } else {
+                    context.resources
+                        .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y)
+                        .toFloat()
+                }
+            // Since we're visually centering the bubbles on tablet, use total screen height rather
+            // than the available height.
+            val height =
+                if (isTablet) {
+                    positioner.screenRect.height()
+                } else {
+                    positioner.availableRect.height()
+                }
+            val offsetPercent = (desiredY / height).coerceIn(0f, 1f)
+            val allowableStackRegion =
+                positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+            return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
+        }
+}
diff --git a/libs/WindowManager/Shell/multivalentTestsForDevice b/libs/WindowManager/Shell/multivalentTestsForDevice
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/multivalentTestsForDeviceless b/libs/WindowManager/Shell/multivalentTestsForDeviceless
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 81d9638..bb433db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -176,6 +176,10 @@
     private StatusBarCustomizer mCustomizer;
     private boolean mTrackingLatency;
 
+    // Keep previous navigation type before remove mBackNavigationInfo.
+    @BackNavigationInfo.BackTargetType
+    private int mPreviousNavigationType;
+
     public BackAnimationController(
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
@@ -871,6 +875,7 @@
         mShellBackAnimationRegistry.resetDefaultCrossActivity();
         cancelLatencyTracking();
         if (mBackNavigationInfo != null) {
+            mPreviousNavigationType = mBackNavigationInfo.getType();
             mBackNavigationInfo.onBackNavigationFinished(triggerBack);
             mBackNavigationInfo = null;
         }
@@ -983,7 +988,9 @@
                         mShellExecutor.execute(
                                 () -> {
                                     if (!mShellBackAnimationRegistry.cancel(
-                                            mBackNavigationInfo.getType())) {
+                                            mBackNavigationInfo != null
+                                                    ? mBackNavigationInfo.getType()
+                                                    : mPreviousNavigationType)) {
                                         return;
                                     }
                                     if (!mBackGestureStarted) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 8c861c6..bf783e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -197,60 +197,61 @@
                 @NonNull SurfaceControl.Transaction startTransaction,
                 @NonNull SurfaceControl.Transaction finishTransaction,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            if (mType == TYPE_ENTER_PIP_FROM_SPLIT) {
-                return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
-                        finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
-            } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
-                return animateEnterPipFromActivityEmbedding(
-                        info, startTransaction, finishTransaction, finishCallback);
-            } else if (mType == TYPE_DISPLAY_AND_SPLIT_CHANGE) {
-                return false;
-            } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
-                final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
-                        info, startTransaction, finishTransaction, finishCallback);
-                // Consume the transition on remote handler if the leftover handler already handle
-                // this transition. And if it cannot, the transition will be handled by remote
-                // handler, so don't consume here.
-                // Need to check leftOverHandler as it may change in
-                // #animateOpenIntentWithRemoteAndPip
-                if (handledToPip && mHasRequestToRemote
-                        && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
-                    mPlayer.getRemoteTransitionHandler().onTransitionConsumed(
-                            transition, false, null);
-                }
-                return handledToPip;
-            } else if (mType == TYPE_RECENTS_DURING_SPLIT) {
-                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                    final TransitionInfo.Change change = info.getChanges().get(i);
-                    // Pip auto-entering info might be appended to recent transition like pressing
-                    // home-key in 3-button navigation. This offers split handler the opportunity to
-                    // handle split to pip animation.
-                    if (mPipHandler.isEnteringPip(change, info.getType())
-                            && mSplitHandler.getSplitItemPosition(change.getLastParent())
-                            != SPLIT_POSITION_UNDEFINED) {
-                        return animateEnterPipFromSplit(
-                                this, info, startTransaction, finishTransaction, finishCallback,
-                                mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+            switch (mType) {
+                case TYPE_ENTER_PIP_FROM_SPLIT:
+                    return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+                case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+                    return animateEnterPipFromActivityEmbedding(
+                            info, startTransaction, finishTransaction, finishCallback);
+                case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+                    return false;
+                case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+                    final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
+                            info, startTransaction, finishTransaction, finishCallback);
+                    // Consume the transition on remote handler if the leftover handler already
+                    // handle this transition. And if it cannot, the transition will be handled by
+                    // remote handler, so don't consume here.
+                    // Need to check leftOverHandler as it may change in
+                    // #animateOpenIntentWithRemoteAndPip
+                    if (handledToPip && mHasRequestToRemote
+                            && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+                        mPlayer.getRemoteTransitionHandler().onTransitionConsumed(
+                                transition, false, null);
                     }
-                }
+                    return handledToPip;
+                case TYPE_RECENTS_DURING_SPLIT:
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        final TransitionInfo.Change change = info.getChanges().get(i);
+                        // Pip auto-entering info might be appended to recent transition like
+                        // pressing home-key in 3-button navigation. This offers split handler the
+                        // opportunity to handle split to pip animation.
+                        if (mPipHandler.isEnteringPip(change, info.getType())
+                                && mSplitHandler.getSplitItemPosition(change.getLastParent())
+                                != SPLIT_POSITION_UNDEFINED) {
+                            return animateEnterPipFromSplit(
+                                    this, info, startTransaction, finishTransaction, finishCallback,
+                                    mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+                        }
+                    }
 
-                return animateRecentsDuringSplit(
-                        info, startTransaction, finishTransaction, finishCallback);
-            } else if (mType == TYPE_KEYGUARD) {
-                return animateKeyguard(this, info, startTransaction, finishTransaction,
-                        finishCallback, mKeyguardHandler, mPipHandler);
-            } else if (mType == TYPE_RECENTS_DURING_KEYGUARD) {
-                return animateRecentsDuringKeyguard(
-                        info, startTransaction, finishTransaction, finishCallback);
-            } else if (mType == TYPE_RECENTS_DURING_DESKTOP) {
-                return animateRecentsDuringDesktop(
-                        info, startTransaction, finishTransaction, finishCallback);
-            } else if (mType == TYPE_UNFOLD) {
-                return animateUnfold(
-                        info, startTransaction, finishTransaction, finishCallback);
-            } else {
-                throw new IllegalStateException(
-                        "Starting mixed animation without a known mixed type? " + mType);
+                    return animateRecentsDuringSplit(
+                            info, startTransaction, finishTransaction, finishCallback);
+                case TYPE_KEYGUARD:
+                    return animateKeyguard(this, info, startTransaction, finishTransaction,
+                            finishCallback, mKeyguardHandler, mPipHandler);
+                case TYPE_RECENTS_DURING_KEYGUARD:
+                    return animateRecentsDuringKeyguard(
+                            info, startTransaction, finishTransaction, finishCallback);
+                case TYPE_RECENTS_DURING_DESKTOP:
+                    return animateRecentsDuringDesktop(
+                            info, startTransaction, finishTransaction, finishCallback);
+                case TYPE_UNFOLD:
+                    return animateUnfold(
+                            info, startTransaction, finishTransaction, finishCallback);
+                default:
+                    throw new IllegalStateException(
+                            "Starting mixed animation without a known mixed type? " + mType);
             }
         }
 
@@ -457,79 +458,100 @@
                 @NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            if (mType == TYPE_DISPLAY_AND_SPLIT_CHANGE) {
-                // queue since no actual animation.
-            } else if (mType == TYPE_ENTER_PIP_FROM_SPLIT) {
-                if (mAnimType == ANIM_TYPE_GOING_HOME) {
-                    boolean ended = mSplitHandler.end();
-                    // If split couldn't end (because it is remote), then don't end everything else
-                    // since we have to play out the animation anyways.
-                    if (!ended) return;
+            switch (mType) {
+                case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+                    // queue since no actual animation.
+                    break;
+                case TYPE_ENTER_PIP_FROM_SPLIT:
+                    if (mAnimType == ANIM_TYPE_GOING_HOME) {
+                        boolean ended = mSplitHandler.end();
+                        // If split couldn't end (because it is remote), then don't end everything
+                        // else since we have to play out the animation anyways.
+                        if (!ended) return;
+                        mPipHandler.end();
+                        if (mLeftoversHandler != null) {
+                            mLeftoversHandler.mergeAnimation(
+                                    transition, info, t, mergeTarget, finishCallback);
+                        }
+                    } else {
+                        mPipHandler.end();
+                    }
+                    break;
+                case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+                    mPipHandler.end();
+                    mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
+                            finishCallback);
+                    break;
+                case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
                     mPipHandler.end();
                     if (mLeftoversHandler != null) {
-                        mLeftoversHandler.mergeAnimation(
-                                transition, info, t, mergeTarget, finishCallback);
+                        mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                                finishCallback);
                     }
-                } else {
-                    mPipHandler.end();
-                }
-            } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
-                mPipHandler.end();
-                mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
-                        finishCallback);
-            } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
-                mPipHandler.end();
-                if (mLeftoversHandler != null) {
-                    mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
-                            finishCallback);
-                }
-            } else if (mType == TYPE_RECENTS_DURING_SPLIT) {
-                if (mSplitHandler.isPendingEnter(transition)) {
-                    // Recents -> enter-split means that we are switching from one pair to
-                    // another pair.
-                    mAnimType = ANIM_TYPE_PAIR_TO_PAIR;
-                }
-                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-            } else if (mType == TYPE_KEYGUARD) {
-                mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-            } else if (mType == TYPE_RECENTS_DURING_KEYGUARD) {
-                if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
-                    DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT);
-                    if (animateKeyguard(
-                            this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
-                        finishCallback.onTransitionFinished(null);
+                    break;
+                case TYPE_RECENTS_DURING_SPLIT:
+                    if (mSplitHandler.isPendingEnter(transition)) {
+                        // Recents -> enter-split means that we are switching from one pair to
+                        // another pair.
+                        mAnimType = ANIM_TYPE_PAIR_TO_PAIR;
                     }
-                }
-                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-            } else if (mType == TYPE_RECENTS_DURING_DESKTOP) {
-                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-            } else if (mType == TYPE_UNFOLD) {
-                mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-            } else {
-                throw new IllegalStateException(
-                        "Playing a mixed transition with unknown type? " + mType);
+                    mLeftoversHandler.mergeAnimation(
+                            transition, info, t, mergeTarget, finishCallback);
+                    break;
+                case TYPE_KEYGUARD:
+                    mKeyguardHandler.mergeAnimation(
+                            transition, info, t, mergeTarget, finishCallback);
+                    break;
+                case TYPE_RECENTS_DURING_KEYGUARD:
+                    if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+                        DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT);
+                        if (animateKeyguard(this, info, t, mFinishT, mFinishCB, mKeyguardHandler,
+                                mPipHandler)) {
+                            finishCallback.onTransitionFinished(null);
+                        }
+                    }
+                    mLeftoversHandler.mergeAnimation(
+                            transition, info, t, mergeTarget, finishCallback);
+                    break;
+                case TYPE_RECENTS_DURING_DESKTOP:
+                    mLeftoversHandler.mergeAnimation(
+                            transition, info, t, mergeTarget, finishCallback);
+                    break;
+                case TYPE_UNFOLD:
+                    mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                    break;
+                default:
+                    throw new IllegalStateException(
+                            "Playing a mixed transition with unknown type? " + mType);
             }
         }
 
         void onTransitionConsumed(
                 @NonNull IBinder transition, boolean aborted,
                 @Nullable SurfaceControl.Transaction finishT) {
-            if (mType == TYPE_ENTER_PIP_FROM_SPLIT) {
-                mPipHandler.onTransitionConsumed(transition, aborted, finishT);
-            } else if (mType == TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
-                mPipHandler.onTransitionConsumed(transition, aborted, finishT);
-                mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
-            } else if (mType == TYPE_RECENTS_DURING_SPLIT) {
-                mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
-            } else if (mType == TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
-                mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
-            } else if (mType == TYPE_KEYGUARD) {
-                mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
-            } else if (mType == TYPE_RECENTS_DURING_DESKTOP) {
-                mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
-            } else if (mType == TYPE_UNFOLD) {
-                mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
+            switch (mType) {
+                case TYPE_ENTER_PIP_FROM_SPLIT:
+                    mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+                    break;
+                case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+                    mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+                    mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
+                    break;
+                case TYPE_RECENTS_DURING_SPLIT:
+                case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+                case TYPE_RECENTS_DURING_DESKTOP:
+                    mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+                    break;
+                case TYPE_KEYGUARD:
+                    mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+                    break;
+                case TYPE_UNFOLD:
+                    mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
+                    break;
+                default:
+                    break;
             }
+
             if (mHasRequestToRemote) {
                 mPlayer.getRemoteTransitionHandler().onTransitionConsumed(
                         transition, aborted, finishT);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
deleted file mode 100644
index 6ebee73..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ /dev/null
@@ -1,602 +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.wm.shell.bubbles;
-
-import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Insets;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests operations and the resulting state managed by {@link BubblePositioner}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class BubblePositionerTest extends ShellTestCase {
-
-    private BubblePositioner mPositioner;
-
-    @Before
-    public void setUp() {
-        WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        mPositioner = new BubblePositioner(mContext, windowManager);
-    }
-
-    @Test
-    public void testUpdate() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1000, 1200);
-        Rect availableRect = new Rect(screenBounds);
-        availableRect.inset(insets);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
-        assertThat(mPositioner.isLandscape()).isFalse();
-        assertThat(mPositioner.isLargeScreen()).isFalse();
-        assertThat(mPositioner.getInsets()).isEqualTo(insets);
-    }
-
-    @Test
-    public void testShowBubblesVertically_phonePortrait() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.showBubblesVertically()).isFalse();
-    }
-
-    @Test
-    public void testShowBubblesVertically_phoneLandscape() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLandscape().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.isLandscape()).isTrue();
-        assertThat(mPositioner.showBubblesVertically()).isTrue();
-    }
-
-    @Test
-    public void testShowBubblesVertically_tablet() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.showBubblesVertically()).isTrue();
-    }
-
-    /** If a resting position hasn't been set, calling it will return the default position. */
-    @Test
-    public void testGetRestingPosition_returnsDefaultPosition() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        PointF restingPosition = mPositioner.getRestingPosition();
-        PointF defaultPosition = mPositioner.getDefaultStartPosition();
-
-        assertThat(restingPosition).isEqualTo(defaultPosition);
-    }
-
-    /** If a resting position has been set, it'll return that instead of the default position. */
-    @Test
-    public void testGetRestingPosition_returnsRestingPosition() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        PointF restingPosition = new PointF(100, 100);
-        mPositioner.setRestingPosition(restingPosition);
-
-        assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition);
-    }
-
-    /** Test that the default resting position on phone is in upper left. */
-    @Test
-    public void testGetRestingPosition_bubble_onPhone() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testGetRestingPosition_bubble_onPhone_RTL() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    /** Test that the default resting position on tablet is middle left. */
-    @Test
-    public void testGetRestingPosition_chatBubble_onTablet() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testGetRestingPosition_chatBubble_onTablet_RTL() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    /** Test that the default resting position on tablet is middle right. */
-    @Test
-    public void testGetDefaultPosition_appBubble_onTablet() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
-        assertThat(startPosition.x).isEqualTo(allowableStackRegion.right);
-        assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testGetRestingPosition_appBubble_onTablet_RTL() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
-        assertThat(startPosition.x).isEqualTo(allowableStackRegion.left);
-        assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testHasUserModifiedDefaultPosition_false() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
-        mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition());
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-    }
-
-    @Test
-    public void testHasUserModifiedDefaultPosition_true() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
-        mPositioner.setRestingPosition(new PointF(0, 100));
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
-    }
-
-    @Test
-    public void testGetExpandedViewHeight_max() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT);
-    }
-
-    @Test
-    public void testGetExpandedViewHeight_customHeight_valid() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        final int minHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_default_height);
-        Bubble bubble = new Bubble("key",
-                mock(ShortcutInfo.class),
-                minHeight + 100 /* desiredHeight */,
-                0 /* desiredHeightResId */,
-                "title",
-                0 /* taskId */,
-                null /* locus */,
-                true /* isDismissable */,
-                directExecutor(),
-                mock(Bubbles.BubbleMetadataFlagListener.class));
-
-        // Ensure the height is the same as the desired value
-        assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(
-                bubble.getDesiredHeight(mContext));
-    }
-
-
-    @Test
-    public void testGetExpandedViewHeight_customHeight_tooSmall() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Bubble bubble = new Bubble("key",
-                mock(ShortcutInfo.class),
-                10 /* desiredHeight */,
-                0 /* desiredHeightResId */,
-                "title",
-                0 /* taskId */,
-                null /* locus */,
-                true /* isDismissable */,
-                directExecutor(),
-                mock(Bubbles.BubbleMetadataFlagListener.class));
-
-        // Ensure the height is the same as the minimum value
-        final int minHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_default_height);
-        assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight);
-    }
-
-    @Test
-    public void testGetMaxExpandedViewHeight_onLargeTablet() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        int manageButtonHeight =
-                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
-        int pointerWidth = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_pointer_width);
-        int expandedViewPadding = mContext.getResources().getDimensionPixelSize(R
-                .dimen.bubble_expanded_view_padding);
-        float expectedHeight = 1800 - 2 * 20 - manageButtonHeight - pointerWidth
-                - expandedViewPadding * 2;
-        assertThat(((float) mPositioner.getMaxExpandedViewHeight(false /* isOverflow */)))
-                .isWithin(0.1f).of(expectedHeight);
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_largeScreen_true() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isTrue();
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_largeScreen_false() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_smallTablet_false() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setSmallTablet()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_phone_false() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
-    }
-
-    @Test
-    public void testExpandedViewY_phoneLandscape() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height so it'll always be top aligned
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_phonePortrait() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // Always top aligned in phone portrait
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_smallTabletLandscape() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setSmallTablet()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height which is always top aligned on small tablets
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_smallTabletPortrait() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setSmallTablet()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height which is always top aligned on small tablets
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_largeScreenLandscape() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height which is always top aligned on landscape, large tablet
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_largeScreenPortrait() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        int manageButtonHeight =
-                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
-        int manageButtonPlusMargin = manageButtonHeight + 2
-                * mContext.getResources().getDimensionPixelSize(
-                        R.dimen.bubble_manage_button_margin);
-        int pointerWidth = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_pointer_width);
-
-        final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom
-                - manageButtonPlusMargin
-                - mPositioner.getExpandedViewHeightForLargeScreen()
-                - pointerWidth;
-
-        // Bubbles are bottom aligned on portrait, large tablet
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(expectedExpandedViewY);
-    }
-
-    /**
-     * Calculates the Y position bubbles should be placed based on the config. Based on
-     * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
-     * {@link BubbleStackView.RelativeStackPosition}.
-     */
-    private float getDefaultYPosition() {
-        final boolean isTablet = mPositioner.isLargeScreen();
-
-        // On tablet the position is centered, on phone it is an offset from the top.
-        final float desiredY = isTablet
-                ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
-                : mContext.getResources().getDimensionPixelOffset(
-                        R.dimen.bubble_stack_starting_offset_y);
-        // Since we're visually centering the bubbles on tablet, use total screen height rather
-        // than the available height.
-        final float height = isTablet
-                ? mPositioner.getScreenRect().height()
-                : mPositioner.getAvailableRect().height();
-        float offsetPercent = desiredY / height;
-        offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
-        final RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent;
-    }
-
-    /**
-     * Sets up window manager to return config values based on what you need for the test.
-     * By default it sets up a portrait phone without any insets.
-     */
-    private static class ConfigBuilder {
-        private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
-        private boolean mIsLargeScreen = false;
-        private boolean mIsSmallTablet = false;
-        private boolean mIsLandscape = false;
-        private boolean mIsRtl = false;
-        private Insets mInsets = Insets.of(0, 0, 0, 0);
-
-        public ConfigBuilder setScreenBounds(Rect screenBounds) {
-            mScreenBounds = screenBounds;
-            return this;
-        }
-
-        public ConfigBuilder setLargeScreen() {
-            mIsLargeScreen = true;
-            return this;
-        }
-
-        public ConfigBuilder setSmallTablet() {
-            mIsSmallTablet = true;
-            return this;
-        }
-
-        public ConfigBuilder setLandscape() {
-            mIsLandscape = true;
-            return this;
-        }
-
-        public ConfigBuilder setRtl() {
-            mIsRtl = true;
-            return this;
-        }
-
-        public ConfigBuilder setInsets(Insets insets) {
-            mInsets = insets;
-            return this;
-        }
-
-        private DeviceConfig build() {
-            return new DeviceConfig(mIsLargeScreen, mIsSmallTablet, mIsLandscape, mIsRtl,
-                    mScreenBounds, mInsets);
-        }
-    }
-}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 6c3172a..d58c872 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -56,6 +56,7 @@
 
 bool Properties::debugLayersUpdates = false;
 bool Properties::debugOverdraw = false;
+bool Properties::debugTraceGpuResourceCategories = false;
 bool Properties::showDirtyRegions = false;
 bool Properties::skipEmptyFrames = true;
 bool Properties::useBufferAge = true;
@@ -151,10 +152,12 @@
 
     skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false);
 
-    SkAndroidFrameworkTraceUtil::setEnableTracing(
-            base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false));
+    bool skiaBroadTracing = base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false);
+    SkAndroidFrameworkTraceUtil::setEnableTracing(skiaBroadTracing);
     SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents(
             base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false));
+    debugTraceGpuResourceCategories =
+            base::GetBoolProperty(PROPERTY_TRACE_GPU_RESOURCES, skiaBroadTracing);
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bca57e9..b956fac 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -143,6 +143,15 @@
 #define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
 
 /**
+ * Might split Skia's GPU resource utilization into separate tracing tracks (slow).
+ *
+ * Aggregate total and purgeable numbers will still be reported under a "misc" track when this is
+ * disabled, they just won't be split into distinct categories. Results may vary depending on GPU
+ * backend/API, and the category mappings defined in ATraceMemoryDump's hardcoded sResourceMap.
+ */
+#define PROPERTY_TRACE_GPU_RESOURCES "debug.hwui.trace_gpu_resources"
+
+/**
  * Allows broad recording of Skia drawing commands.
  *
  * If disabled, a very minimal set of trace events *may* be recorded.
@@ -254,6 +263,7 @@
 
     static bool debugLayersUpdates;
     static bool debugOverdraw;
+    static bool debugTraceGpuResourceCategories;
     static bool showDirtyRegions;
     // TODO: Remove after stabilization period
     static bool skipEmptyFrames;
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
index 234f42d..756b937 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -20,6 +20,8 @@
 
 #include <cstring>
 
+#include "GrDirectContext.h"
+
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
@@ -114,8 +116,16 @@
 
 /**
  * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ *
+ * gpuMemoryIsAlreadyInDump must be true if GrDirectContext::dumpMemoryStatistics(...) was called
+ * with this tracer, false otherwise. Leaving this false allows this function to quickly query total
+ * and purgable GPU memory without the caller having to spend time in
+ * GrDirectContext::dumpMemoryStatistics(...) first, which iterates over every resource in the GPU
+ * cache. This can save significant time, but buckets all GPU memory into a single "misc" track,
+ * which may be a loss of granularity depending on the GPU backend and the categories defined in
+ * sResourceMap.
  */
-void ATraceMemoryDump::logTraces() {
+void ATraceMemoryDump::logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext) {
     // Accumulate data from last dumpName
     recordAndResetCountersIfNeeded("");
     uint64_t hwui_all_frame_memory = 0;
@@ -126,6 +136,20 @@
             ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory);
         }
     }
+
+    if (!gpuMemoryIsAlreadyInDump && grContext) {
+        // Total GPU memory
+        int gpuResourceCount;
+        size_t gpuResourceBytes;
+        grContext->getResourceCacheUsage(&gpuResourceCount, &gpuResourceBytes);
+        hwui_all_frame_memory += (uint64_t)gpuResourceBytes;
+        ATRACE_INT64("HWUI Misc Memory", gpuResourceBytes);
+
+        // Purgable subset of GPU memory
+        size_t purgeableGpuResourceBytes = grContext->getResourceCachePurgeableBytes();
+        ATRACE_INT64("Purgeable HWUI Misc Memory", purgeableGpuResourceBytes);
+    }
+
     ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory);
 }
 
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
index 4592711..777d1a2 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.h
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <GrDirectContext.h>
 #include <SkString.h>
 #include <SkTraceMemoryDump.h>
 
@@ -50,7 +51,7 @@
 
     void startFrame();
 
-    void logTraces();
+    void logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext);
 
 private:
     std::string mLastDumpName;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 30d4612..eb4d494 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -269,13 +269,14 @@
     cancelDestroyContext();
     mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
     if (ATRACE_ENABLED()) {
+        ATRACE_NAME("dumpingMemoryStatistics");
         static skiapipeline::ATraceMemoryDump tracer;
         tracer.startFrame();
         SkGraphics::DumpMemoryStatistics(&tracer);
-        if (mGrContext) {
+        if (mGrContext && Properties::debugTraceGpuResourceCategories) {
             mGrContext->dumpMemoryStatistics(&tracer);
         }
-        tracer.logTraces();
+        tracer.logTraces(Properties::debugTraceGpuResourceCategories, mGrContext.get());
     }
 }
 
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 79b2155..660884a 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -433,7 +433,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;"
+        errorLine1="        boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) &gt; 0;"
         errorLine2="                             ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -444,7 +444,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;"
+        errorLine1="        boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) &gt; 0;"
         errorLine2="                              ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -455,7 +455,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;"
+        errorLine1="        boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) &gt; 0;"
         errorLine2="                                ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -466,7 +466,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;"
+        errorLine1="        boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) &gt; 0;"
         errorLine2="                            ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -562,4 +562,4 @@
             column="74"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8f8cd0b..9616b5d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -719,7 +719,7 @@
         mImpl.transfer(
                 controller.getRoutingSessionInfo(),
                 route,
-                android.os.Process.myUserHandle(),
+                Process.myUserHandle(),
                 mContext.getPackageName());
     }
 
@@ -1560,7 +1560,7 @@
             UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
             String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
 
-            return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle)
+            return Objects.equals(Process.myUserHandle(), transferInitiatorUserHandle)
                     && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
         }
 
@@ -2327,11 +2327,7 @@
 
             List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
             RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-            transfer(
-                    targetSession,
-                    route,
-                    android.os.Process.myUserHandle(),
-                    mContext.getPackageName());
+            transfer(targetSession, route, Process.myUserHandle(), mContext.getPackageName());
         }
 
         @Override
@@ -3198,8 +3194,12 @@
                 return;
             }
 
-            requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE,
-                    android.os.Process.myUserHandle(), mContext.getPackageName());
+            requestCreateController(
+                    controller,
+                    route,
+                    MANAGER_REQUEST_ID_NONE,
+                    Process.myUserHandle(),
+                    mContext.getPackageName());
         }
 
         @Override
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 262f5f1..096e8ad 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -50,8 +50,16 @@
     private static final String TAG = "BluetoothMidiDevice";
     private static final boolean DEBUG = false;
 
-    private static final int DEFAULT_PACKET_SIZE = 20;
-    private static final int MAX_PACKET_SIZE = 512;
+    // Bluetooth services should subtract 5 bytes from the MTU for headers.
+    private static final int HEADER_SIZE = 5;
+    // Min MTU size for BLE
+    private static final int MIN_L2CAP_MTU = 23;
+    // 23 (min L2CAP MTU) - 5 (header size)
+    private static final int DEFAULT_PACKET_SIZE = MIN_L2CAP_MTU - HEADER_SIZE;
+    // Max MTU size on Android
+    private static final int MAX_ANDROID_MTU = 517;
+    // 517 (max Android MTU) - 5 (header size)
+    private static final int MAX_PACKET_SIZE = MAX_ANDROID_MTU - HEADER_SIZE;
 
     //  Bluetooth MIDI Gatt service UUID
     private static final UUID MIDI_SERVICE = UUID.fromString(
@@ -135,8 +143,8 @@
                         // switch to receiving notifications
                         mBluetoothGatt.readCharacteristic(characteristic);
 
-                        // Request higher MTU size
-                        if (!gatt.requestMtu(MAX_PACKET_SIZE)) {
+                        // Request max MTU size
+                        if (!gatt.requestMtu(MAX_ANDROID_MTU)) {
                             Log.e(TAG, "request mtu failed");
                             mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
                             mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
@@ -204,8 +212,15 @@
         public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
             Log.d(TAG, "onMtuChanged callback received. mtu: " + mtu + ", status: " + status);
             if (status == BluetoothGatt.GATT_SUCCESS) {
-                mPacketEncoder.setMaxPacketSize(Math.min(mtu, MAX_PACKET_SIZE));
-                mPacketDecoder.setMaxPacketSize(Math.min(mtu, MAX_PACKET_SIZE));
+                int packetSize = Math.min(mtu - HEADER_SIZE, MAX_PACKET_SIZE);
+                if (packetSize <= 0) {
+                    Log.e(TAG, "onMtuChanged non-positive packet size: " + packetSize);
+                    packetSize = DEFAULT_PACKET_SIZE;
+                } else if (packetSize < DEFAULT_PACKET_SIZE) {
+                    Log.w(TAG, "onMtuChanged small packet size: " + packetSize);
+                }
+                mPacketEncoder.setMaxPacketSize(packetSize);
+                mPacketDecoder.setMaxPacketSize(packetSize);
             } else {
                 mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
                 mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
deleted file mode 100644
index 9d16f32d..0000000
--- a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ 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.
-  -->
-
-<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/autofill_light_colorControlHighlight">
-    <item android:id="@android:id/mask">
-        <color android:color="@android:color/white"/>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 2f0c83b..5becc86 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+  ~ Copyright (C) 2024 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -23,8 +23,8 @@
         android:shape="rectangle"
         android:top="1dp">
         <shape>
-            <corners android:radius="28dp" />
-            <solid android:color="@android:color/system_surface_container_high_light" />
+            <corners android:radius="16dp" />
+            <solid android:color="@color/dropdown_container" />
         </shape>
     </item>
 </ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
deleted file mode 100644
index e4e9f7a..0000000
--- a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
+++ /dev/null
@@ -1,37 +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.
-  -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:id="@android:id/content"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="@style/autofill.Dataset">
-    <ImageView
-        android:id="@android:id/icon1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_centerVertical="true"
-        android:layout_alignParentStart="true"
-        android:background="@null"/>
-    <TextView
-        android:id="@android:id/text1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_toEndOf="@android:id/icon1"
-        style="@style/autofill.TextAppearance"/>
-
-</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
new file mode 100644
index 0000000..cb6c6b4
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -0,0 +1,45 @@
+<!--
+  ~ 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.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@android:id/content"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxWidth="@dimen/autofill_dropdown_layout_width"
+                android:elevation="3dp">
+
+        <ImageView
+            android:id="@android:id/icon1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_alignParentStart="true"
+            android:background="@null"/>
+        <TextView
+            android:id="@android:id/text1"
+            android:layout_width="@dimen/autofill_dropdown_text_width"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@android:id/icon1"
+            style="@style/autofill.TextTitle"/>
+        <TextView
+            android:id="@android:id/text2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/text1"
+            android:layout_toEndOf="@android:id/icon1"
+            style="@style/autofill.TextSubtitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index 63b9f24..dcb7ef9 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -16,23 +16,8 @@
 
 <!-- Color palette -->
 <resources>
-    <color name="autofill_light_colorPrimary">@color/primary_material_light</color>
-    <color name="autofill_light_colorAccent">@color/accent_material_light</color>
-    <color name="autofill_light_colorControlHighlight">@color/ripple_material_light</color>
-    <color name="autofill_light_colorButtonNormal">@color/button_material_light</color>
-
-    <!-- Text colors -->
-    <color name="autofill_light_textColorPrimary">@color/abc_primary_text_material_light</color>
-    <color name="autofill_light_textColorSecondary">@color/abc_secondary_text_material_light</color>
-    <color name="autofill_light_textColorHint">@color/abc_hint_foreground_material_light</color>
-    <color name="autofill_light_textColorHintInverse">@color/abc_hint_foreground_material_dark
-    </color>
-    <color name="autofill_light_textColorHighlight">@color/highlighted_text_material_light</color>
-    <color name="autofill_light_textColorLink">@color/autofill_light_colorAccent</color>
-
     <!-- These colors are used for Remote Views. -->
-    <color name="background_dark_mode">#0E0C0B</color>
-    <color name="background">#F1F3F4</color>
-    <color name="text_primary_dark_mode">#DFDEDB</color>
-    <color name="text_primary">#202124</color>
+    <color name="text_primary">#1A1B20</color>
+    <color name="text_secondary">#44474F</color>
+    <color name="dropdown_container">#F3F3FA</color>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 67003a3..2a4719d 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -17,6 +17,12 @@
   -->
 
 <resources>
-    <dimen name="autofill_view_padding">16dp</dimen>
-    <dimen name="autofill_icon_size">16dp</dimen>
+    <dimen name="autofill_view_top_padding">12dp</dimen>
+    <dimen name="autofill_view_right_padding">24dp</dimen>
+    <dimen name="autofill_view_bottom_padding">12dp</dimen>
+    <dimen name="autofill_view_left_padding">16dp</dimen>
+    <dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
+    <dimen name="autofill_icon_size">24dp</dimen>
+    <dimen name="autofill_dropdown_layout_width">296dp</dimen>
+    <dimen name="autofill_dropdown_text_width">240dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/styles.xml b/packages/CredentialManager/res/values/styles.xml
index 4a5761a..7de941e 100644
--- a/packages/CredentialManager/res/values/styles.xml
+++ b/packages/CredentialManager/res/values/styles.xml
@@ -15,24 +15,13 @@
   -->
 
 <resources>
-    <style name="autofill.TextAppearance.Small" parent="@style/autofill.TextAppearance">
-        <item name="android:textSize">12sp</item>
-    </style>
-
-
-    <style name="autofill.Dataset" parent="">
-        <item name="android:background">@drawable/autofill_light_selectable_item_background</item>
-    </style>
-
-    <style name="autofill.TextAppearance" parent="">
-        <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
-        <item name="android:textColorHint">@color/autofill_light_textColorHint</item>
-        <item name="android:textColorHighlight">@color/autofill_light_textColorHighlight</item>
-        <item name="android:textColorLink">@color/autofill_light_textColorLink</item>
+    <style name="autofill.TextTitle" parent="">
+        <item name="android:fontFamily">google-sans-medium</item>
         <item name="android:textSize">14sp</item>
     </style>
 
-    <style name="autofill.TextAppearance.Primary">
-        <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
+    <style name="autofill.TextSubtitle" parent="">
+        <item name="android:fontFamily">google-sans-text</item>
+        <item name="android:textSize">12sp</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index b2c23a4..58467af 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -57,6 +57,7 @@
 import com.android.credentialmanager.getflow.ProviderDisplayInfo
 import com.android.credentialmanager.getflow.toProviderDisplayInfo
 import com.android.credentialmanager.ktx.credentialEntry
+import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.model.get.ProviderInfo
 import org.json.JSONException
@@ -313,12 +314,14 @@
         var i = 0
         var datasetAdded = false
 
-        val duplicateDisplayNames: MutableMap<String, Boolean> = mutableMapOf()
+        val duplicateDisplayNamesForPasskeys: MutableMap<String, Boolean> = mutableMapOf()
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach {
             val credentialEntry = it.sortedCredentialEntryList.first()
-            credentialEntry.displayName?.let {displayName ->
-                val duplicateEntry = duplicateDisplayNames.contains(displayName)
-                duplicateDisplayNames[displayName] = duplicateEntry
+            if (credentialEntry.credentialType == CredentialType.PASSKEY) {
+                credentialEntry.displayName?.let { displayName ->
+                    val duplicateEntry = duplicateDisplayNamesForPasskeys.contains(displayName)
+                    duplicateDisplayNamesForPasskeys[displayName] = duplicateEntry
+                }
             }
         }
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
@@ -355,12 +358,19 @@
                 } else {
                     spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
                 }
-                val displayName : String = primaryEntry.displayName ?: primaryEntry.userName
+                val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+                        && primaryEntry.displayName != null) {
+                    primaryEntry.displayName!!
+                } else {
+                    primaryEntry.userName
+                }
                 val sliceBuilder = InlineSuggestionUi
                         .newContentBuilder(pendingIntent)
                         .setTitle(displayName)
                 sliceBuilder.setStartIcon(icon)
-                if (duplicateDisplayNames[displayName] == true) {
+                if (primaryEntry.credentialType ==
+                        CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
+                        == true) {
                     sliceBuilder.setSubtitle(primaryEntry.userName)
                 }
                 inlinePresentation = InlinePresentation(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 4dc7f00..e039dea 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -21,6 +21,7 @@
 import android.widget.RemoteViews
 import androidx.core.content.ContextCompat
 import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.CredentialType
 import android.graphics.drawable.Icon
 
 class RemoteViewsFactory {
@@ -29,48 +30,87 @@
         private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds"
         private const val setMaxHeightMethodName = "setMaxHeight"
         private const val setBackgroundResourceMethodName = "setBackgroundResource"
+        private const val bulletPoint = "\u2022"
+        private const val passwordCharacterLength = 15
 
         fun createDropdownPresentation(
                 context: Context,
                 icon: Icon,
                 credentialEntryInfo: CredentialEntryInfo
         ): RemoteViews {
-            val padding = context.resources.getDimensionPixelSize(com.android
-                    .credentialmanager.R.dimen.autofill_view_padding)
             var layoutId: Int = com.android.credentialmanager.R.layout
-                    .autofill_dataset_left_with_item_tag_hint
+                    .credman_dropdown_presentation_layout
             val remoteViews = RemoteViews(context.packageName, layoutId)
-            setRemoteViewsPaddings(remoteViews, padding)
-            val textColorPrimary = getTextColorPrimary(isDarkMode(context), context);
-            remoteViews.setTextColor(android.R.id.text1, textColorPrimary);
-            remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
-
+            if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
+                return remoteViews
+            }
+            setRemoteViewsPaddings(remoteViews, context)
+            if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
+                val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
+                remoteViews.setTextViewText(android.R.id.text1, displayName)
+                val secondaryText = if (credentialEntryInfo.displayName != null)
+                    (credentialEntryInfo.userName + " " + bulletPoint + " "
+                            + credentialEntryInfo.credentialTypeDisplayName
+                            + " " + bulletPoint + " " + credentialEntryInfo.providerDisplayName)
+                else (credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " "
+                        + credentialEntryInfo.providerDisplayName)
+                remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+            } else {
+                remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
+                remoteViews.setTextViewText(android.R.id.text2,
+                        bulletPoint.repeat(passwordCharacterLength))
+            }
+            val textColorPrimary = ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.text_primary)
+            remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+            val textColorSecondary = ContextCompat.getColor(context, com.android
+                    .credentialmanager.R.color.text_secondary)
+            remoteViews.setTextColor(android.R.id.text2, textColorSecondary)
             remoteViews.setImageViewIcon(android.R.id.icon1, icon);
             remoteViews.setBoolean(
                     android.R.id.icon1, setAdjustViewBoundsMethodName, true);
             remoteViews.setInt(
                     android.R.id.icon1,
-                     setMaxHeightMethodName,
+                    setMaxHeightMethodName,
                     context.resources.getDimensionPixelSize(
                             com.android.credentialmanager.R.dimen.autofill_icon_size));
-            val drawableId = if (isDarkMode(context))
-                com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one_dark
-            else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+            val drawableId =
+                    com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
             remoteViews.setInt(
                     android.R.id.content, setBackgroundResourceMethodName, drawableId);
             return remoteViews
         }
 
         private fun setRemoteViewsPaddings(
-                remoteViews: RemoteViews,
-                padding: Int) {
-            val halfPadding = padding / 2
+                remoteViews: RemoteViews, context: Context) {
+            val leftPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_left_padding)
+            val iconToTextPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_icon_to_text_padding)
+            val rightPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_right_padding)
+            val topPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_top_padding)
+            val bottomPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+            remoteViews.setViewPadding(
+                    android.R.id.icon1,
+                    leftPadding,
+                    /* top=*/0,
+                    /* right=*/0,
+                    /* bottom=*/0)
             remoteViews.setViewPadding(
                     android.R.id.text1,
-                    halfPadding,
-                    halfPadding,
-                    halfPadding,
-                    halfPadding)
+                    iconToTextPadding,
+                    /* top=*/topPadding,
+                    /* right=*/rightPadding,
+                    /* bottom=*/0)
+            remoteViews.setViewPadding(
+                    android.R.id.text2,
+                    iconToTextPadding,
+                    /* top=*/0,
+                    /* right=*/rightPadding,
+                    /* bottom=*/bottomPadding)
         }
 
         private fun isDarkMode(context: Context): Boolean {
@@ -78,11 +118,5 @@
                     Configuration.UI_MODE_NIGHT_MASK
             return currentNightMode == Configuration.UI_MODE_NIGHT_YES
         }
-
-        private fun getTextColorPrimary(darkMode: Boolean, context: Context): Int {
-            return if (darkMode) ContextCompat.getColor(
-                    context, com.android.credentialmanager.R.color.text_primary_dark_mode)
-            else ContextCompat.getColor(context, com.android.credentialmanager.R.color.text_primary)
-        }
     }
 }
diff --git a/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml b/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
index 7f16517..8127e1a 100644
--- a/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
+++ b/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -8,7 +8,7 @@
         errorLine2="                                 ~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java"
-            line="78"
+            line="79"
             column="34"/>
     </issue>
 
@@ -19,7 +19,7 @@
         errorLine2="                                   ~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java"
-            line="90"
+            line="91"
             column="36"/>
     </issue>
 
@@ -30,7 +30,7 @@
         errorLine2="                                             ~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="43"
+            line="45"
             column="46"/>
     </issue>
 
@@ -41,7 +41,7 @@
         errorLine2="        ~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="65"
+            line="67"
             column="9"/>
     </issue>
 
@@ -52,7 +52,7 @@
         errorLine2="        ~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="72"
+            line="74"
             column="9"/>
     </issue>
 
@@ -63,7 +63,7 @@
         errorLine2="        ~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="80"
+            line="82"
             column="9"/>
     </issue>
 
@@ -74,8 +74,8 @@
         errorLine2="                         ~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="105"
+            line="107"
             column="26"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 8b16d64..5da4b95 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -61,7 +61,6 @@
         "src/**/*.kt",
     ],
     lint: {
-        baseline_filename: "lint-baseline.xml",
         extra_check_modules: ["SettingsLibLintChecker"],
     },
 }
diff --git a/packages/SettingsLib/EmergencyNumber/lint-baseline.xml b/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
index e9c687f..13bf5f5 100644
--- a/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
+++ b/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -8,7 +8,7 @@
         errorLine2="                                        ~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="77"
+            line="81"
             column="41"/>
     </issue>
 
@@ -19,7 +19,7 @@
         errorLine2="                                            ~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="78"
+            line="82"
             column="45"/>
     </issue>
 
@@ -30,18 +30,18 @@
         errorLine2="                                                             ~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="78"
+            line="82"
             column="62"/>
     </issue>
 
     <issue
         id="NewApi"
         message="Call requires API level 29 (current min is 21): `android.telephony.TelephonyManager#getEmergencyNumberList`"
-        errorLine1="        Map&lt;Integer, List&lt;EmergencyNumber>> allLists = mTelephonyManager.getEmergencyNumberList("
+        errorLine1="        Map&lt;Integer, List&lt;EmergencyNumber&gt;&gt; allLists = mTelephonyManager.getEmergencyNumberList("
         errorLine2="                                                                         ~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="173"
+            line="177"
             column="74"/>
     </issue>
 
@@ -52,7 +52,7 @@
         errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="196"
+            line="200"
             column="41"/>
     </issue>
 
@@ -63,7 +63,7 @@
         errorLine2="                                                                    ~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="219"
+            line="223"
             column="69"/>
     </issue>
 
@@ -74,7 +74,7 @@
         errorLine2="                                        ~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="234"
+            line="238"
             column="41"/>
     </issue>
 
@@ -85,8 +85,8 @@
         errorLine2="                                                   ~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="251"
+            line="255"
             column="52"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index d9f74da..010a6ce 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -28,7 +28,4 @@
         "com.android.extservices",
         "com.android.healthfitness",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml b/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml
deleted file mode 100644
index cfa64a4..0000000
--- a/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="`@android:dimen/config_restrictedIconSize` requires API level 29 (current min is 28)"
-        errorLine1='    &lt;dimen name="settingslib_restricted_icon_size"&gt;@android:dimen/config_restrictedIconSize&lt;/dimen&gt;'
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml"
-            line="21"
-            column="52"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml b/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
index 26d05a6..45a07fe 100644
--- a/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
@@ -1,38 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
-        errorLine1="        context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
-        errorLine2="                                                       ~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
-            line="97"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
-        errorLine1="        return um.getUserProfiles().contains(UserHandle.of(userId));"
-        errorLine2="                                                        ~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
-            line="140"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 23): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
-        errorLine1="            adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
-            line="75"
-            column="34"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -58,6 +25,28 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level 26 (current min is 23): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
+        errorLine1="            adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+            line="75"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
+        errorLine1="        context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
+        errorLine2="                                                       ~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+            line="97"
+            column="56"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 29 (current min is 23): `android.content.Context#startActivityAsUser`"
         errorLine1="        context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~">
@@ -67,4 +56,15 @@
             column="17"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
+        errorLine1="        return um.getUserProfiles().contains(UserHandle.of(userId));"
+        errorLine2="                                                        ~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+            line="120"
+            column="57"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/SchedulesProvider/lint-baseline.xml b/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
index 0744710..db6a882 100644
--- a/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
+++ b/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
diff --git a/packages/SettingsLib/SearchProvider/lint-baseline.xml b/packages/SettingsLib/SearchProvider/lint-baseline.xml
index 53346e0..3cfca1d 100644
--- a/packages/SettingsLib/SearchProvider/lint-baseline.xml
+++ b/packages/SettingsLib/SearchProvider/lint-baseline.xml
@@ -1,27 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableResource`"
-        errorLine1="            super("
-        errorLine2="            ~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="107"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 23 (current min is 21): `android.provider.SearchIndexableResource`"
-        errorLine1="    public static final class SearchIndexableIntentResource extends SearchIndexableResource {"
-        errorLine2="                                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="97"
-            column="69"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -36,6 +14,39 @@
 
     <issue
         id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexablesContract#INDEXABLES_XML_RES_COLUMNS`"
+        errorLine1="        final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="45"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#rank`"
+        errorLine1="                    .add(XmlResource.COLUMN_RANK, indexableResource.rank)"
+        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="50"
+            column="51"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableResource#xmlResId`"
+        errorLine1="                    .add(XmlResource.COLUMN_XML_RESID, indexableResource.xmlResId)"
+        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="51"
+            column="56"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#className`"
         errorLine1="                    .add(XmlResource.COLUMN_CLASS_NAME, indexableResource.className)"
         errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -58,6 +69,39 @@
 
     <issue
         id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
+        errorLine1="                            indexableResource.intentTargetClass);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="56"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 23 (current min is 21): `android.provider.SearchIndexableResource`"
+        errorLine1="    public static final class SearchIndexableIntentResource extends SearchIndexableResource {"
+        errorLine2="                                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="97"
+            column="69"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableResource`"
+        errorLine1="            super("
+        errorLine2="            ~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="107"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentAction`"
         errorLine1='                this.intentAction = "android.intent.action.MAIN";'
         errorLine2="                ~~~~~~~~~~~~~~~~~">
@@ -81,17 +125,6 @@
     <issue
         id="NewApi"
         message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
-        errorLine1="                            indexableResource.intentTargetClass);"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="56"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
         errorLine1="            this.intentTargetClass = className;"
         errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -100,37 +133,4 @@
             column="13"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#rank`"
-        errorLine1="                    .add(XmlResource.COLUMN_RANK, indexableResource.rank)"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="50"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableResource#xmlResId`"
-        errorLine1="                    .add(XmlResource.COLUMN_XML_RESID, indexableResource.xmlResId)"
-        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="51"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexablesContract#INDEXABLES_XML_RES_COLUMNS`"
-        errorLine1="        final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);"
-        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="45"
-            column="54"/>
-    </issue>
-
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index a9974dc..514ad669 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -39,6 +39,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -68,6 +71,7 @@
     ) {
         val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
         Button(
+            modifier = Modifier.semantics { role = Role.DropdownList },
             onClick = { expanded = true },
             colors = ButtonDefaults.buttonColors(
                 containerColor = SettingsTheme.colorScheme.spinnerHeaderContainer,
diff --git a/packages/SettingsLib/Tile/lint-baseline.xml b/packages/SettingsLib/Tile/lint-baseline.xml
index 326ec0d..56b1bca 100644
--- a/packages/SettingsLib/Tile/lint-baseline.xml
+++ b/packages/SettingsLib/Tile/lint-baseline.xml
@@ -1,55 +1,59 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
-        message="Call requires API level 24 (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="        controllers.forEach(controller -&gt; {"
-        errorLine2="                    ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java"
-            line="79"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#createWithResource`">
+        message="Call requires API level 29 (current min is 21): `android.os.Parcel#writeBoolean`"
+        errorLine1="        dest.writeBoolean(this instanceof ProviderTile);"
+        errorLine2="             ~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="312"/>
+            line="114"
+            column="14"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#setTint`">
+        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#createWithResource`"
+        errorLine1="            final Icon icon = Icon.createWithResource(componentInfo.packageName, iconResId);"
+        errorLine2="                                   ~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="318"/>
+            line="326"
+            column="36"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 29 (current min is 21): `android.os.Parcel#readBoolean`">
+        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#setTint`"
+        errorLine1="                icon.setTint(tintColor);"
+        errorLine2="                     ~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="373"/>
+            line="332"
+            column="22"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 29 (current min is 21): `android.os.Parcel#writeBoolean`">
+        message="Call requires API level 29 (current min is 21): `android.os.Parcel#readBoolean`"
+        errorLine1="            final boolean isProviderTile = source.readBoolean();"
+        errorLine2="                                                  ~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="108"/>
+            line="387"
+            column="51"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 21): `android.content.Context#getAttributionSource`">
+        message="Call requires API level 31 (current min is 21): `android.content.Context#getAttributionSource`"
+        errorLine1="            return provider.call(context.getAttributionSource(),"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java"
-            line="565"/>
+            line="601"
+            column="42"/>
     </issue>
 
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/Utils/lint-baseline.xml b/packages/SettingsLib/Utils/lint-baseline.xml
index 3fcd56c..2f6cc3a 100644
--- a/packages/SettingsLib/Utils/lint-baseline.xml
+++ b/packages/SettingsLib/Utils/lint-baseline.xml
@@ -1,26 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
-        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
-        errorLine1="                            mContext.getPackageName(), 0, UserHandle.of(managedUserId)"
-        errorLine2="                                                                     ~~">
+        message="Call requires API level 24 (current min is 23): `android.os.UserManager#isManagedProfile`"
+        errorLine1="        return context.getSystemService(UserManager.class).isManagedProfile(userId)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="119"
-            column="70"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
-        errorLine1="                        intent, 0, UserHandle.of(managedUserId));"
-        errorLine2="                                              ~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="150"
-            column="47"/>
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/applications/AppUtils.java"
+            line="62"
+            column="60"/>
     </issue>
 
     <issue
@@ -36,35 +25,13 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 24 (current min is 21): `android.os.UserManager#isManagedProfile`"
-        errorLine1="            if (mUserManager.isManagedProfile(id)) {"
-        errorLine2="                             ~~~~~~~~~~~~~~~~">
+        message="Call requires API level 29 (current min is 21): `android.content.Context#startActivityAsUser`"
+        errorLine1="            activityContext.startActivityAsUser(intent, UserHandle.of(userId));"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="173"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 23): `android.os.UserManager#isManagedProfile`"
-        errorLine1="        return context.getSystemService(UserManager.class).isManagedProfile(userId)"
-        errorLine2="                                                           ~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/applications/AppUtils.java"
-            line="62"
-            column="60"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 21): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
-        errorLine1="        return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="163"
-            column="37"/>
+            line="80"
+            column="29"/>
     </issue>
 
     <issue
@@ -80,13 +47,13 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 29 (current min is 21): `android.content.Context#startActivityAsUser`"
-        errorLine1="            activityContext.startActivityAsUser(intent, UserHandle.of(userId));"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~~">
+        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
+        errorLine1="                            mContext.getPackageName(), 0, UserHandle.of(managedUserId)"
+        errorLine2="                                                                     ~~">
         <location
             file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="80"
-            column="29"/>
+            line="119"
+            column="70"/>
     </issue>
 
     <issue
@@ -102,6 +69,28 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
+        errorLine1="                        intent, 0, UserHandle.of(managedUserId));"
+        errorLine2="                                              ~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+            line="150"
+            column="47"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 26 (current min is 21): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
+        errorLine1="        return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+            line="163"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 30 (current min is 21): `android.os.UserManager#getAllProfiles`"
         errorLine1="        List&lt;UserHandle&gt; allProfiles = mUserManager.getAllProfiles();"
         errorLine2="                                                    ~~~~~~~~~~~~~~">
@@ -111,4 +100,15 @@
             column="53"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 21): `android.os.UserManager#isManagedProfile`"
+        errorLine1="            if (mUserManager.isManagedProfile(id)) {"
+        errorLine2="                             ~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+            line="173"
+            column="30"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/lint-baseline.xml b/packages/SettingsLib/lint-baseline.xml
deleted file mode 100644
index d6a23fd..0000000
--- a/packages/SettingsLib/lint-baseline.xml
+++ /dev/null
@@ -1,204 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.bluetooth.BluetoothDevice#setAlias`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java"
-            line="584"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiInfo#getSubscriptionId`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java"
-            line="248"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiInfo#getSubscriptionId`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java"
-            line="278"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiManager#registerSubsystemRestartTrackingCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="201"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiManager#unregisterSubsystemRestartTrackingCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="208"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.UserManager#isUserForeground`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java"
-            line="78"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#isDataCapable`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/Utils.java"
-            line="498"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#isDataCapable`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java"
-            line="225"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#registerTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="215"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#registerTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="86"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#unregisterTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="222"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#unregisterTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="88"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java"
-            line="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java"
-            line="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java"
-            line="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.net.wifi.WifiManager.SubsystemRestartTrackingCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="64"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="125"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.CarrierNetworkListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="124"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DataActivityListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="123"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DataConnectionStateListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="122"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DisplayInfoListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="126"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.ServiceStateListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="120"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.SignalStrengthsListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="121"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="79"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="119"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 3b14712..390c9d2e 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,9 +12,6 @@
     visibility: ["//visibility:private"],
     srcs: ["interface-src/**/*.java"],
     host_supported: true,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 android_library {
diff --git a/packages/SettingsLib/search/lint-baseline.xml b/packages/SettingsLib/search/lint-baseline.xml
index 7ec512b..61cdb05 100644
--- a/packages/SettingsLib/search/lint-baseline.xml
+++ b/packages/SettingsLib/search/lint-baseline.xml
@@ -1,16 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableData`"
-        errorLine1="        super(context);"
-        errorLine2="        ~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java"
-            line="62"
-            column="9"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -23,4 +12,15 @@
             column="41"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableData`"
+        errorLine1="        super(context);"
+        errorLine2="        ~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java"
+            line="62"
+            column="9"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
index 5bc27195..943e3fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -70,10 +70,8 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        if (isAvailable()) {
-            mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
-            updateConnectivity();
-        }
+        mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+        updateConnectivity();
     }
 
     @Override
@@ -84,16 +82,16 @@
     @SuppressLint("HardwareIds")
     @Override
     protected void updateConnectivity() {
+        if (mWifiManager == null || mWifiMacAddress == null) {
+            return;
+        }
+
         final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
         String macAddress = null;
         if (macAddresses != null && macAddresses.length > 0) {
             macAddress = macAddresses[0];
         }
 
-        if (mWifiMacAddress == null) {
-            return;
-        }
-
         if (TextUtils.isEmpty(macAddress) || macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
             mWifiMacAddress.setSummary(R.string.status_unavailable);
         } else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index d47527a..2fea6a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -7,7 +7,6 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Close
@@ -21,10 +20,8 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
@@ -41,7 +38,6 @@
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.transform
 
@@ -66,7 +62,6 @@
  * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
  * handling and transitions before the full Flexiglass layout is ready.
  */
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalCoroutinesApi::class)
 @Composable
 fun CommunalContainer(
     modifier: Modifier = Modifier,
@@ -83,8 +78,8 @@
             transitions = sceneTransitions,
         )
 
-    // Don't show hub mode UI if keyguard is present. This is important since we're in the shade,
-    // which can be opened from many locations.
+    // Don't show hub mode UI if keyguard is not present. This is important since we're in the
+    // shade, which can be opened from many locations.
     val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
 
     // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
@@ -102,56 +97,30 @@
         onDispose { viewModel.setTransitionState(null) }
     }
 
-    Box(modifier = modifier.fillMaxSize()) {
-        SceneTransitionLayout(
-            state = sceneTransitionLayoutState,
-            modifier = Modifier.fillMaxSize(),
-            edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+    SceneTransitionLayout(
+        state = sceneTransitionLayoutState,
+        modifier = modifier.fillMaxSize(),
+        edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+    ) {
+        scene(
+            TransitionSceneKey.Blank,
+            userActions =
+                mapOf(
+                    Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
+                )
         ) {
-            scene(
-                TransitionSceneKey.Blank,
-                userActions =
-                    mapOf(
-                        Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to
-                            TransitionSceneKey.Communal
-                    )
-            ) {
-                BlankScene { showSceneTransitionLayout = false }
-            }
-
-            scene(
-                TransitionSceneKey.Communal,
-                userActions =
-                    mapOf(
-                        Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to
-                            TransitionSceneKey.Blank
-                    ),
-            ) {
-                CommunalScene(viewModel, modifier = modifier)
-            }
+            BlankScene { showSceneTransitionLayout = false }
         }
 
-        // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't
-        //  block touches anymore.
-        Box(
-            modifier =
-                Modifier.fillMaxSize()
-                    // Offsetting to the left so that edge swipe to open the hub still works. This
-                    // does mean that the very right edge of the hub won't refresh the screen
-                    // timeout, but should be good enough for a temporary solution.
-                    .offset(x = -ContainerDimensions.EdgeSwipeSize)
-                    .pointerInteropFilter {
-                        viewModel.onUserActivity()
-                        if (
-                            sceneTransitionLayoutState.transitionState.currentScene ==
-                                TransitionSceneKey.Blank
-                        ) {
-                            viewModel.onOuterTouch(it)
-                            return@pointerInteropFilter true
-                        }
-                        false
-                    }
-        )
+        scene(
+            TransitionSceneKey.Communal,
+            userActions =
+                mapOf(
+                    Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
+                ),
+        ) {
+            CommunalScene(viewModel, modifier = modifier)
+        }
     }
 }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c8461d2..02d30c5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -197,7 +197,6 @@
         whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
 
         featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
         featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
         featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
         featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index ff6fd43..54510a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -21,7 +21,6 @@
 import android.app.smartspace.SmartspaceTarget
 import android.appwidget.AppWidgetHost
 import android.content.ComponentName
-import android.os.PowerManager
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -41,13 +40,11 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -64,8 +61,6 @@
 @RunWith(AndroidJUnit4::class)
 class CommunalEditModeViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
-    @Mock private lateinit var shadeViewController: ShadeViewController
-    @Mock private lateinit var powerManager: PowerManager
     @Mock private lateinit var appWidgetHost: AppWidgetHost
     @Mock private lateinit var uiEventLogger: UiEventLogger
 
@@ -97,8 +92,6 @@
             CommunalEditModeViewModel(
                 withDeps.communalInteractor,
                 appWidgetHost,
-                Provider { shadeViewController },
-                powerManager,
                 mediaHost,
                 uiEventLogger,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8e3f664..a776062 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.communal.view.viewmodel
 
 import android.app.smartspace.SmartspaceTarget
-import android.os.PowerManager
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -36,12 +35,10 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
@@ -58,8 +55,6 @@
 @RunWith(AndroidJUnit4::class)
 class CommunalViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
-    @Mock private lateinit var shadeViewController: ShadeViewController
-    @Mock private lateinit var powerManager: PowerManager
 
     private lateinit var testScope: TestScope
 
@@ -92,8 +87,6 @@
                 withDeps.communalInteractor,
                 WidgetInteractionHandler(mock()),
                 withDeps.tutorialInteractor,
-                Provider { shadeViewController },
-                powerManager,
                 mediaHost,
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bd1c310..c104977 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -16,32 +16,45 @@
 
 package com.android.systemui.qs.tiles.base.actions
 
+import android.app.PendingIntent
+import android.content.ComponentName
 import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatcher
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
-
-    @Mock private lateinit var activityStarted: ActivityStarter
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var activityStarter: ActivityStarter
 
     lateinit var underTest: QSTileIntentUserInputHandler
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        underTest = QSTileIntentUserInputHandlerImpl(activityStarted)
+        underTest = QSTileIntentUserInputHandlerImpl(activityStarter, packageManager, user)
     }
 
     @Test
@@ -50,6 +63,103 @@
 
         underTest.handle(null, intent)
 
-        verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+        verify(activityStarter).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+    }
+
+    @Test
+    fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
+        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+
+        underTest.handle(null, pendingIntent, true)
+
+        verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
+    }
+
+    @Test
+    fun testPassesActivityPendingIntentToStarterAsPendingIntentWhenNotRequestingActivityStart() {
+        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+
+        underTest.handle(null, pendingIntent, false)
+
+        verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
+    }
+
+    @Test
+    fun testPassNonActivityPendingIntentAndRequestStartingActivity_findsIntentAndStarts() {
+        val pendingIntent =
+            mock<PendingIntent> {
+                whenever(isActivity).thenReturn(false)
+                whenever(creatorPackage).thenReturn(ORIGINAL_PACKAGE)
+            }
+        setUpQueryResult(listOf(createActivityInfo(testResolvedComponent, exported = true)))
+
+        underTest.handle(null, pendingIntent, true)
+
+        val expectedIntent =
+            Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_LAUNCHER)
+                .setPackage(null)
+                .addFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                )
+                .setComponent(testResolvedComponent)
+
+        verify(activityStarter)
+            .postStartActivityDismissingKeyguard(
+                argThat(IntentMatcher(expectedIntent)),
+                eq(0),
+                any()
+            )
+    }
+
+    @Test
+    fun testPassNonActivityPendingIntentAndDoNotRequestStartingActivity_doesNotStartActivity() {
+        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) }
+
+        underTest.handle(null, pendingIntent, false)
+
+        verify(activityStarter, never())
+            .postStartActivityDismissingKeyguard(any(Intent::class.java), eq(0), any())
+    }
+
+    private fun createActivityInfo(
+        componentName: ComponentName,
+        exported: Boolean = false,
+    ): ActivityInfo {
+        return ActivityInfo().apply {
+            packageName = componentName.packageName
+            name = componentName.className
+            this.exported = exported
+        }
+    }
+
+    private fun setUpQueryResult(infos: List<ActivityInfo>) {
+        `when`(
+                packageManager.queryIntentActivitiesAsUser(
+                    any(Intent::class.java),
+                    any(ResolveInfoFlags::class.java),
+                    eq(user.identifier)
+                )
+            )
+            .thenReturn(infos.map { ResolveInfo().apply { activityInfo = it } })
+    }
+
+    private class IntentMatcher(intent: Intent) : ArgumentMatcher<Intent> {
+        private val expectedIntent = intent
+        override fun matches(argument: Intent?): Boolean {
+            return argument?.action.equals(expectedIntent.action) &&
+                argument?.`package`.equals(expectedIntent.`package`) &&
+                argument?.component?.equals(expectedIntent.component)!! &&
+                argument?.categories?.equals(expectedIntent.categories)!! &&
+                argument?.flags?.equals(expectedIntent.flags)!!
+        }
+    }
+
+    companion object {
+        private const val ORIGINAL_PACKAGE = "original_pkg"
+        private const val TEST_PACKAGE = "test_pkg"
+        private const val TEST_COMPONENT_CLASS_NAME = "test_component_class_name"
+        private val testResolvedComponent = ComponentName(TEST_PACKAGE, TEST_COMPONENT_CLASS_NAME)
+        private val user = UserHandle.of(0)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
index e44c849..be2da17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
@@ -18,42 +18,25 @@
 
 import android.app.AlarmManager.AlarmClockInfo
 import android.app.PendingIntent
-import android.content.Intent
 import android.provider.AlarmClock
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
 import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AlarmTileUserActionInteractorTest : SysuiTestCase() {
-    private lateinit var activityStarter: ActivityStarter
-    private lateinit var intentCaptor: ArgumentCaptor<Intent>
-    private lateinit var pendingIntentCaptor: ArgumentCaptor<PendingIntent>
-
-    lateinit var underTest: AlarmTileUserActionInteractor
-
-    @Before
-    fun setup() {
-        activityStarter = mock<ActivityStarter>()
-        intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
-        pendingIntentCaptor = ArgumentCaptor.forClass(PendingIntent::class.java)
-        underTest = AlarmTileUserActionInteractor(activityStarter)
-    }
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+    private val underTest = AlarmTileUserActionInteractor(inputHandler)
 
     @Test
     fun handleClickWithDefaultIntent() = runTest {
@@ -62,21 +45,21 @@
 
         underTest.handleInput(click(inputModel))
 
-        verify(activityStarter)
-            .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0), nullable())
-        assertThat(intentCaptor.value.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+        }
     }
 
     @Test
     fun handleClickWithPendingIntent() = runTest {
-        val expectedIntent: PendingIntent = mock<PendingIntent>()
+        val expectedIntent = mock<PendingIntent>()
         val alarmInfo = AlarmClockInfo(1L, expectedIntent)
         val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo)
 
         underTest.handleInput(click(inputModel))
 
-        verify(activityStarter)
-            .postStartActivityDismissingKeyguard(capture(pendingIntentCaptor), nullable())
-        assertThat(pendingIntentCaptor.value).isEqualTo(expectedIntent)
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOnePendingIntentInput {
+            assertThat(it.pendingIntent).isEqualTo(expectedIntent)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
new file mode 100644
index 0000000..8a0400d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.statusbar.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteInputRepositoryImplTest : SysuiTestCase() {
+    @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
+
+    private lateinit var testScope: TestScope
+    private lateinit var underTest: RemoteInputRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testScope = TestScope()
+        underTest = RemoteInputRepositoryImpl(remoteInputManager)
+    }
+
+    @Test
+    fun isRemoteInputActive_updatesOnChange() =
+        testScope.runTest {
+            val active by collectLastValue(underTest.isRemoteInputActive)
+            runCurrent()
+            assertThat(active).isFalse()
+
+            val callback = withArgCaptor {
+                verify(remoteInputManager).addControllerCallback(capture())
+            }
+
+            callback.onRemoteInputActive(true)
+            runCurrent()
+            assertThat(active).isTrue()
+
+            callback.onRemoteInputActive(false)
+            runCurrent()
+            assertThat(active).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
new file mode 100644
index 0000000..12469dd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.statusbar.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteInputInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
+    private val underTest = kosmos.remoteInputInteractor
+
+    @Test
+    fun isRemoteInputActive_true() =
+        testScope.runTest {
+            val active by collectLastValue(underTest.isRemoteInputActive)
+
+            fakeRemoteInputRepository.isRemoteInputActive.value = true
+            runCurrent()
+
+            assertThat(active).isTrue()
+        }
+
+    @Test
+    fun isRemoteInputActive_false() =
+        testScope.runTest {
+            val active by collectLastValue(underTest.isRemoteInputActive)
+
+            fakeRemoteInputRepository.isRemoteInputActive.value = false
+            runCurrent()
+
+            assertThat(active).isFalse()
+        }
+}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 05106c9..326c7ef 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,9 +34,6 @@
     srcs: [
         ":statslog-SystemUI-java-gen",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 android_library {
@@ -74,9 +71,6 @@
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
     kotlincflags: ["-Xjvm-default=all"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -88,9 +82,6 @@
     static_kotlin_stdlib: false,
     java_version: "1.8",
     min_sdk_version: "current",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -110,7 +101,4 @@
     },
     java_version: "1.8",
     min_sdk_version: "current",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/packages/SystemUI/shared/lint-baseline.xml b/packages/SystemUI/shared/lint-baseline.xml
deleted file mode 100644
index 4bd6729..0000000
--- a/packages/SystemUI/shared/lint-baseline.xml
+++ /dev/null
@@ -1,708 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.os.RemoteException#rethrowFromSystemServer`"
-        errorLine1="            throw e.rethrowFromSystemServer();"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java"
-            line="90"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.graphics.Bitmap#getHardwareBuffer`"
-        errorLine1="                mBuffer != null ? mBuffer.getHardwareBuffer() : null, mRect);"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java"
-            line="39"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `new android.graphics.ParcelableColorSpace`"
-        errorLine1="                        ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="57"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `new android.graphics.ParcelableColorSpace`"
-        errorLine1="                        : new ParcelableColorSpace(bitmap.getColorSpace());"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="58"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.graphics.Bitmap#getHardwareBuffer`"
-        errorLine1="        bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="61"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `ParcelableColorSpace` to `Parcelable` requires API level 31 (current min is 26)"
-        errorLine1="        bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);"
-        errorLine2="                                              ~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="62"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.graphics.Bitmap#wrapHardwareBuffer`"
-        errorLine1="        return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="84"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.graphics.ParcelableColorSpace#getColorSpace`"
-        errorLine1="                colorSpace.getColorSpace());"
-        errorLine2="                           ~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="85"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java"
-            line="122"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.util.ArraySet`"
-        errorLine1="        mPluginActions = new ArraySet&lt;>(mSharedPrefs.getStringSet(PLUGIN_ACTIONS, null));"
-        errorLine2="                         ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java"
-            line="41"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.util.ArraySet`"
-        errorLine1="        return new ArraySet&lt;>(mPluginActions);"
-        errorLine2="               ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java"
-            line="45"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 28 (current min is 26): `android.graphics.Bitmap#createBitmap`"
-        errorLine1="        return Bitmap.createBitmap(picture);"
-        errorLine2="                      ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java"
-            line="113"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Builder`"
-        errorLine1="            mSurface = new SurfaceControl.Builder()"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="116"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#setName`"
-        errorLine1="                    .setName(&quot;Transition Unrotate&quot;)"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="117"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#setParent`"
-        errorLine1="                    .setParent(parent)"
-        errorLine2="                     ~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="119"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#build`"
-        errorLine1="                    .build();"
-        errorLine2="                     ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="120"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#reparent`"
-        errorLine1="            t.reparent(child, mSurface);"
-        errorLine2="              ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="137"
-            column="15"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="            SurfaceControl.Transaction t = new SurfaceControl.Transaction();"
-        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="143"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#reparent`"
-        errorLine1="                t.reparent(mRotateChildren.get(i), rootLeash);"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="145"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="            t.apply();"
-        errorLine2="              ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="148"
-            column="15"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                        t.setLayer(counterLauncher.mSurface, launcherLayer);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="200"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                        t.setLayer(counterLauncher.mSurface, info.getChanges().size() * 3);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="206"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                            t.setLayer(leash, info.getChanges().size() * 3 - i);"
-        errorLine2="                              ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="216"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="                        t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="223"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                            t.setLayer(counterWallpaper.mSurface, -1);"
-        errorLine2="                              ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="233"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="                t.apply();"
-        errorLine2="                  ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="238"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#taskId`"
-        errorLine1="        taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="101"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
-        errorLine1="        taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="192"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#isRunning`"
-        errorLine1="            isNotInRecents = !change.getTaskInfo().isRunning;"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="116"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#isRunning`"
-        errorLine1="            isNotInRecents = !change.getTaskInfo().isRunning;"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="210"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#release`"
-        errorLine1="        leash.mSurfaceControl.release();"
-        errorLine2="                              ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="159"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#release`"
-        errorLine1="            mStartLeash.release();"
-        errorLine2="                        ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="161"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                        t.setLayer(change.getLeash(), info.getChanges().size() * 3 - i);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
-            line="97"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="                    t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);"
-        errorLine2="                      ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
-            line="105"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="                t.apply();"
-        errorLine2="                  ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
-            line="107"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#isValid`"
-        errorLine1="        return mSurfaceControl != null &amp;&amp; mSurfaceControl.isValid();"
-        errorLine2="                                                          ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java"
-            line="41"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#release`"
-        errorLine1="            mSurfaceControlViewHost.release();"
-        errorLine2="                                    ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java"
-            line="61"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.SurfaceView#getHostToken`"
-        errorLine1="        bundle.putBinder(KEY_HOST_TOKEN, surfaceView.getHostToken());"
-        errorLine2="                                                     ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
-            line="34"
-            column="54"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceView#getSurfaceControl`"
-        errorLine1="        bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl());"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
-            line="35"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `SurfaceControl` to `Parcelable` requires API level 29 (current min is 26)"
-        errorLine1="        bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl());"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
-            line="35"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#isValid`"
-        errorLine1="                if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {"
-        errorLine2="                                                                              ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="107"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="                Transaction t = new Transaction();"
-        errorLine2="                                ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="113"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="                    t.apply();"
-        errorLine2="                      ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="122"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="                t.setAlpha(surface, alpha);"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="361"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                t.setLayer(surface, layer);"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="364"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#origActivity`"
-        errorLine1="            ComponentName sourceComponent = t.origActivity != null"
-        errorLine2="                                            ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="73"
-            column="45"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#origActivity`"
-        errorLine1="                    ? t.origActivity"
-        errorLine2="                      ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="75"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
-        errorLine1="            this.id = t.taskId;"
-        errorLine2="                      ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="78"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#baseIntent`"
-        errorLine1="            this.baseIntent = t.baseIntent;"
-        errorLine2="                              ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="80"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskDescription`"
-        errorLine1="        ActivityManager.TaskDescription td = taskInfo.taskDescription;"
-        errorLine2="                                             ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="242"
-            column="46"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#topActivity`"
-        errorLine1="                taskInfo.supportsSplitScreenMultiWindow, isLocked, td, taskInfo.topActivity);"
-        errorLine2="                                                                       ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="246"
-            column="72"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#topActivity`"
-        errorLine1="        return info.topActivity;"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java"
-            line="49"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskDescription`"
-        errorLine1="        return info.taskDescription;"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java"
-            line="53"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#taskId`"
-        errorLine1="        onTaskMovedToFront(taskInfo.taskId);"
-        errorLine2="                           ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java"
-            line="70"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
-        errorLine1="        onTaskMovedToFront(taskInfo.taskId);"
-        errorLine2="                           ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java"
-            line="70"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.graphics.Bitmap#wrapHardwareBuffer`"
-        errorLine1="                thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.getColorSpace());"
-        errorLine2="                                   ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java"
-            line="69"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.app.WallpaperColors#getColorHints`"
-        errorLine1="                    (colors.getColorHints() &amp; WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;"
-        errorLine2="                            ~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TonalCompat.java"
-            line="42"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="        mTransaction = new Transaction();"
-        errorLine2="                       ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="31"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="        mTransaction.apply();"
-        errorLine2="                     ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="35"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setBufferSize`"
-        errorLine1="        mTransaction.setBufferSize(surfaceControl.mSurfaceControl, w, h);"
-        errorLine2="                     ~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="54"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="        mTransaction.setLayer(surfaceControl.mSurfaceControl, z);"
-        errorLine2="                     ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="59"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="        mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha);"
-        errorLine2="                     ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="64"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="            t.apply();"
-        errorLine2="              ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java"
-            line="64"
-            column="15"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
-        errorLine1="                .getFloat(Resources.getSystem().getIdentifier("
-        errorLine2="                 ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperManagerCompat.java"
-            line="46"
-            column="18"/>
-    </issue>
-
-</issues>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index e457ca1..8e5d0da 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -1111,7 +1111,7 @@
     }
 
     private boolean canDisplayUserSwitcher() {
-        return mFeatureFlags.isEnabled(Flags.BOUNCER_USER_SWITCHER);
+        return getContext().getResources().getBoolean(R.bool.config_enableBouncerUserSwitcher);
     }
 
     private void configureMode() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 28f48ce..84708a4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -17,17 +17,12 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import android.content.ComponentName
-import android.os.PowerManager
-import android.os.SystemClock
-import android.view.MotionEvent
 import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
-import javax.inject.Provider
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
@@ -35,8 +30,6 @@
 /** The base view model for the communal hub. */
 abstract class BaseCommunalViewModel(
     private val communalInteractor: CommunalInteractor,
-    private val shadeViewController: Provider<ShadeViewController>,
-    private val powerManager: PowerManager,
     val mediaHost: MediaHost,
 ) {
     val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
@@ -71,26 +64,6 @@
         return true
     }
 
-    // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
-    //  touches anymore.
-    /** Called when a touch is received outside the edge swipe area when hub mode is closed. */
-    fun onOuterTouch(motionEvent: MotionEvent) {
-        // Forward the touch to the shade so that basic gestures like swipe up/down for
-        // shade/bouncer work.
-        shadeViewController.get().handleExternalTouch(motionEvent)
-    }
-
-    // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
-    //  touches anymore.
-    /** Called to refresh the screen timeout when a user touch is received. */
-    fun onUserActivity() {
-        powerManager.userActivity(
-            SystemClock.uptimeMillis(),
-            PowerManager.USER_ACTIVITY_EVENT_TOUCH,
-            0
-        )
-    }
-
     /** A list of all the communal content to be displayed in the communal hub. */
     abstract val communalContent: Flow<List<CommunalContentModel>>
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 0cbf3f13..7faf653 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -23,7 +23,6 @@
 import android.appwidget.AppWidgetHost
 import android.content.ActivityNotFoundException
 import android.content.ComponentName
-import android.os.PowerManager
 import android.widget.RemoteViews
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -32,11 +31,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.util.nullableAtomicReference
 import javax.inject.Inject
 import javax.inject.Named
-import javax.inject.Provider
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -49,11 +46,9 @@
 constructor(
     private val communalInteractor: CommunalInteractor,
     private val appWidgetHost: AppWidgetHost,
-    shadeViewController: Provider<ShadeViewController>,
-    powerManager: PowerManager,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     private val uiEventLogger: UiEventLogger,
-) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
 
     private companion object {
         private const val KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 1c69685..7a96fab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
-import android.os.PowerManager
 import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
@@ -26,10 +25,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.shade.ShadeViewController
 import javax.inject.Inject
 import javax.inject.Named
-import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
@@ -51,10 +48,8 @@
     private val communalInteractor: CommunalInteractor,
     private val interactionHandler: WidgetInteractionHandler,
     tutorialInteractor: CommunalTutorialInteractor,
-    shadeViewController: Provider<ShadeViewController>,
-    powerManager: PowerManager,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
-) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
     @OptIn(ExperimentalCoroutinesApi::class)
     override val communalContent: Flow<List<CommunalContentModel>> =
         tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 699532c..89a983b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -122,11 +122,6 @@
     val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag("new_unlock_swipe_animation")
     val CHARGING_RIPPLE = resourceBooleanFlag(R.bool.flag_charging_ripple, "charging_ripple")
 
-    // TODO(b/254512281): Tracking Bug
-    @JvmField
-    val BOUNCER_USER_SWITCHER =
-        resourceBooleanFlag(R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
-
     // TODO(b/254512676): Tracking Bug
     @JvmField
     val LOCKSCREEN_CUSTOM_CLOCKS =
@@ -377,9 +372,6 @@
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
 
-    // TODO(b/254512758): Tracking Bug
-    @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag("rounded_box_ripple")
-
     // TODO(b/273509374): Tracking Bug
     @JvmField
     val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 52f2759..d7a2aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -18,7 +18,8 @@
 
 import android.animation.ValueAnimator
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -28,6 +29,7 @@
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
@@ -39,13 +41,18 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.ALTERNATE_BOUNCER,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -65,7 +72,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.primaryBouncerShowing,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         powerInteractor.isAwake,
                         keyguardInteractor.isAodAvailable,
                         ::toQuad
@@ -102,20 +109,19 @@
 
     private fun listenForAlternateBouncerToGone() {
         scope.launch {
-            keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { (isKeyguardGoingAway, keyguardState) ->
-                    if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
-                        startTransitionTo(KeyguardState.GONE)
-                    }
+            keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
+                (isKeyguardGoingAway, keyguardState) ->
+                if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+                    startTransitionTo(KeyguardState.GONE)
                 }
+            }
         }
     }
 
     private fun listenForAlternateBouncerToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
                     if (
                         isPrimaryBouncerShowing &&
@@ -139,6 +145,7 @@
     }
 
     companion object {
+        const val TAG = "FromAlternateBouncerTransitionInteractor"
         val TRANSITION_DURATION_MS = 300.milliseconds
         val TO_GONE_DURATION = 500.milliseconds
         val TO_AOD_DURATION = TRANSITION_DURATION_MS
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8584401..fedd63b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -29,6 +30,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -38,12 +40,17 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.AOD,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -58,7 +65,7 @@
                 .dozeTransitionTo(DozeStateModel.FINISH)
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
                         ::Pair
                     ),
@@ -77,7 +84,6 @@
                             } else {
                                 TransitionModeOnCanceled.LAST_VALUE
                             }
-
                         startTransitionTo(
                             toState = toState,
                             modeOnCanceled = modeOnCanceled,
@@ -89,16 +95,13 @@
 
     private fun listenForAodToGone() {
         scope.launch {
-            keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { pair ->
-                    val (biometricUnlockState, keyguardState) = pair
-                    if (
-                        keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
-                    ) {
-                        startTransitionTo(KeyguardState.GONE)
-                    }
+            keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
+                pair ->
+                val (biometricUnlockState, keyguardState) = pair
+                if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
+                    startTransitionTo(KeyguardState.GONE)
                 }
+            }
         }
     }
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -113,6 +116,7 @@
     }
 
     companion object {
+        const val TAG = "FromAodTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = 500.milliseconds
         val TO_GONE_DURATION = DEFAULT_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index eca7088..fcb7698 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -28,6 +29,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -37,13 +39,18 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DOZING,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -57,7 +64,7 @@
             powerInteractor.isAwake
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
                         ::Pair
                     ),
@@ -76,7 +83,7 @@
     private fun listenForDozingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransition) ->
                     if (
                         lastStartedTransition.to == KeyguardState.DOZING &&
@@ -96,6 +103,7 @@
     }
 
     companion object {
+        const val TAG = "FromDozingTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index dac6ef5..a6cdaa8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -28,6 +29,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
@@ -39,12 +41,17 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -64,7 +71,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.dozeTransitionModel,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -88,7 +95,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair,
                     ),
                     ::toTriple
@@ -108,7 +115,7 @@
     private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isBouncerShowing &&
@@ -123,7 +130,7 @@
     private fun listenForDreamingLockscreenHostedToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransitionStep) ->
                     if (
                         lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED &&
@@ -137,11 +144,7 @@
 
     private fun listenForDreamingLockscreenHostedToDozing() {
         scope.launch {
-            combine(
-                    keyguardInteractor.dozeTransitionModel,
-                    transitionInteractor.startedKeyguardTransitionStep,
-                    ::Pair
-                )
+            combine(keyguardInteractor.dozeTransitionModel, startedKeyguardTransitionStep, ::Pair)
                 .collect { (dozeTransitionModel, lastStartedTransitionStep) ->
                     if (
                         dozeTransitionModel.to == DozeStateModel.DOZE &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7fdcf2f..13ffd63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -28,6 +29,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -37,12 +39,17 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DREAMING,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -66,7 +73,7 @@
     private fun listenForDreamingToOccluded() {
         scope.launch {
             combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple)
+                .sample(startedKeyguardTransitionStep, ::toTriple)
                 .collect { (isOccluded, isDreaming, lastStartedTransition) ->
                     if (
                         isOccluded &&
@@ -82,7 +89,7 @@
     private fun listenForDreamingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransitionStep) ->
                     if (
                         lastStartedTransitionStep.to == KeyguardState.DREAMING &&
@@ -96,11 +103,7 @@
 
     private fun listenForDreamingToAodOrDozing() {
         scope.launch {
-            combine(
-                    keyguardInteractor.dozeTransitionModel,
-                    transitionInteractor.finishedKeyguardState,
-                    ::Pair
-                )
+            combine(keyguardInteractor.dozeTransitionModel, finishedKeyguardState, ::Pair)
                 .collect { (dozeTransitionModel, keyguardState) ->
                     if (keyguardState == KeyguardState.DREAMING) {
                         if (dozeTransitionModel.to == DozeStateModel.DOZE) {
@@ -123,6 +126,7 @@
     }
 
     companion object {
+        const val TAG = "FromDreamingTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = 1167.milliseconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 70c2e6d..19fd7f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -20,18 +20,29 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 
 @SysUISingleton
 class FromGlanceableHubTransitionInteractor
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(fromState = KeyguardState.GLANCEABLE_HUB) {
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Main mainDispatcher: CoroutineDispatcher,
+    @Background bgDispatcher: CoroutineDispatcher,
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.GLANCEABLE_HUB,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
+    ) {
     override fun start() {
         if (!Flags.communalHub()) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 62a0b0e..742790e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -28,6 +29,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -37,13 +39,18 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.GONE,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -57,7 +64,7 @@
     private fun listenForGoneToLockscreen() {
         scope.launch {
             keyguardInteractor.isKeyguardShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardShowing, lastStartedStep) ->
                     if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
                         startTransitionTo(KeyguardState.LOCKSCREEN)
@@ -69,7 +76,7 @@
     private fun listenForGoneToDreamingLockscreenHosted() {
         scope.launch {
             keyguardInteractor.isActiveDreamLockscreenHosted
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isActiveDreamLockscreenHosted, lastStartedStep) ->
                     if (isActiveDreamLockscreenHosted && lastStartedStep.to == KeyguardState.GONE) {
                         startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
@@ -83,7 +90,7 @@
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isActiveDreamLockscreenHosted,
                         ::Pair
                     ),
@@ -106,7 +113,7 @@
             powerInteractor.isAsleep
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cecc653..2d0baa8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,9 +18,9 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launch
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -39,20 +39,25 @@
 import java.util.UUID
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
@@ -61,6 +66,9 @@
 ) :
     TransitionInteractor(
         fromState = KeyguardState.LOCKSCREEN,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -147,12 +155,12 @@
 
     private fun listenForLockscreenToDreaming() {
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
-        scope.launch("$TAG#listenForLockscreenToDreaming") {
+        scope.launch {
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        transitionInteractor.finishedKeyguardState,
+                        startedKeyguardTransitionStep,
+                        finishedKeyguardState,
                         keyguardInteractor.isActiveDreamLockscreenHosted,
                         ::Triple
                     ),
@@ -180,9 +188,9 @@
     }
 
     private fun listenForLockscreenToPrimaryBouncer() {
-        scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
+        scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isBouncerShowing, lastStartedTransitionStep) = pair
                     if (
@@ -195,9 +203,9 @@
     }
 
     private fun listenForLockscreenToAlternateBouncer() {
-        scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
+        scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
                     if (
@@ -213,11 +221,11 @@
     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
     private fun listenForLockscreenToPrimaryBouncerDragging() {
         var transitionId: UUID? = null
-        scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
+        scope.launch {
             shadeRepository.legacyShadeExpansion
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.statusBarState,
                         keyguardInteractor.isKeyguardUnlocked,
                         ::Triple
@@ -225,64 +233,66 @@
                     ::toQuad
                 )
                 .collect { (shadeExpansion, keyguardState, statusBarState, isKeyguardUnlocked) ->
-                    val id = transitionId
-                    if (id != null) {
-                        if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
-                            // An existing `id` means a transition is started, and calls to
-                            // `updateTransition` will control it until FINISHED or CANCELED
-                            var nextState =
-                                if (shadeExpansion == 0f) {
-                                    TransitionState.FINISHED
-                                } else if (shadeExpansion == 1f) {
-                                    TransitionState.CANCELED
-                                } else {
-                                    TransitionState.RUNNING
+                    withContext(mainDispatcher) {
+                        val id = transitionId
+                        if (id != null) {
+                            if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
+                                // An existing `id` means a transition is started, and calls to
+                                // `updateTransition` will control it until FINISHED or CANCELED
+                                var nextState =
+                                    if (shadeExpansion == 0f) {
+                                        TransitionState.FINISHED
+                                    } else if (shadeExpansion == 1f) {
+                                        TransitionState.CANCELED
+                                    } else {
+                                        TransitionState.RUNNING
+                                    }
+                                transitionRepository.updateTransition(
+                                    id,
+                                    1f - shadeExpansion,
+                                    nextState,
+                                )
+
+                                if (
+                                    nextState == TransitionState.CANCELED ||
+                                        nextState == TransitionState.FINISHED
+                                ) {
+                                    transitionId = null
                                 }
-                            transitionRepository.updateTransition(
-                                id,
-                                1f - shadeExpansion,
-                                nextState,
-                            )
 
-                            if (
-                                nextState == TransitionState.CANCELED ||
-                                    nextState == TransitionState.FINISHED
-                            ) {
-                                transitionId = null
-                            }
-
-                            // If canceled, just put the state back
-                            // TODO(b/278086361): This logic should happen in
-                            //  FromPrimaryBouncerInteractor.
-                            if (nextState == TransitionState.CANCELED) {
-                                transitionRepository.startTransition(
-                                    TransitionInfo(
-                                        ownerName = name,
-                                        from = KeyguardState.PRIMARY_BOUNCER,
-                                        to = KeyguardState.LOCKSCREEN,
-                                        animator =
-                                            getDefaultAnimatorForTransitionsToState(
-                                                    KeyguardState.LOCKSCREEN
-                                                )
-                                                .apply { duration = 0 }
+                                // If canceled, just put the state back
+                                // TODO(b/278086361): This logic should happen in
+                                //  FromPrimaryBouncerInteractor.
+                                if (nextState == TransitionState.CANCELED) {
+                                    transitionRepository.startTransition(
+                                        TransitionInfo(
+                                            ownerName = name,
+                                            from = KeyguardState.PRIMARY_BOUNCER,
+                                            to = KeyguardState.LOCKSCREEN,
+                                            animator =
+                                                getDefaultAnimatorForTransitionsToState(
+                                                        KeyguardState.LOCKSCREEN
+                                                    )
+                                                    .apply { duration = 0 }
+                                        )
                                     )
-                                )
+                                }
                             }
-                        }
-                    } else {
-                        // TODO (b/251849525): Remove statusbarstate check when that state is
-                        // integrated into KeyguardTransitionRepository
-                        if (
-                            keyguardState.to == KeyguardState.LOCKSCREEN &&
-                                shadeRepository.legacyShadeTracking.value &&
-                                !isKeyguardUnlocked &&
-                                statusBarState == KEYGUARD
-                        ) {
-                            transitionId =
-                                startTransitionTo(
-                                    toState = KeyguardState.PRIMARY_BOUNCER,
-                                    animator = null, // transition will be manually controlled
-                                )
+                        } else {
+                            // TODO (b/251849525): Remove statusbarstate check when that state is
+                            // integrated into KeyguardTransitionRepository
+                            if (
+                                keyguardState.to == KeyguardState.LOCKSCREEN &&
+                                    shadeRepository.legacyShadeTracking.value &&
+                                    !isKeyguardUnlocked &&
+                                    statusBarState == KEYGUARD
+                            ) {
+                                transitionId =
+                                    startTransitionTo(
+                                        toState = KeyguardState.PRIMARY_BOUNCER,
+                                        animator = null, // transition will be manually controlled
+                                    )
+                            }
                         }
                     }
                 }
@@ -290,7 +300,7 @@
     }
 
     fun dismissKeyguard() {
-        startTransitionTo(KeyguardState.GONE)
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
     }
 
     private fun listenForLockscreenToGone() {
@@ -298,9 +308,9 @@
             return
         }
 
-        scope.launch("$TAG#listenForLockscreenToGone") {
+        scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -315,9 +325,9 @@
             return
         }
 
-        scope.launch("$TAG#listenForLockscreenToGoneDragging") {
+        scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -328,23 +338,22 @@
     }
 
     private fun listenForLockscreenToOccluded() {
-        scope.launch("$TAG#listenForLockscreenToOccluded") {
-            keyguardInteractor.isKeyguardOccluded
-                .sample(transitionInteractor.startedKeyguardState, ::Pair)
-                .collect { (isOccluded, keyguardState) ->
-                    if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
-                        startTransitionTo(KeyguardState.OCCLUDED)
-                    }
+        scope.launch {
+            keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+                (isOccluded, keyguardState) ->
+                if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
+                    startTransitionTo(KeyguardState.OCCLUDED)
                 }
+            }
         }
     }
 
     private fun listenForLockscreenToAodOrDozing() {
-        scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
+        scope.launch {
             powerInteractor.isAsleep
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 6a8555c..40061f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -27,6 +28,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -36,13 +38,18 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.OCCLUDED,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -57,7 +64,7 @@
     private fun listenForOccludedToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.OCCLUDED
@@ -70,13 +77,12 @@
 
     private fun listenForOccludedToDreaming() {
         scope.launch {
-            keyguardInteractor.isAbleToDream
-                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { (isAbleToDream, keyguardState) ->
-                    if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
-                        startTransitionTo(KeyguardState.DREAMING)
-                    }
+            keyguardInteractor.isAbleToDream.sample(finishedKeyguardState, ::Pair).collect {
+                (isAbleToDream, keyguardState) ->
+                if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
+                    startTransitionTo(KeyguardState.DREAMING)
                 }
+            }
         }
     }
 
@@ -86,7 +92,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -111,7 +117,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -135,7 +141,7 @@
             powerInteractor.isAsleep
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -154,7 +160,7 @@
     private fun listenForOccludedToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isAlternateBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isAlternateBouncerShowing &&
@@ -183,6 +189,7 @@
     }
 
     companion object {
+        const val TAG = "FromOccludedTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = 933.milliseconds
         val TO_AOD_DURATION = DEFAULT_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 5f246e1..c62055f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -35,6 +36,7 @@
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -47,8 +49,10 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
@@ -57,6 +61,9 @@
 ) :
     TransitionInteractor(
         fromState = KeyguardState.PRIMARY_BOUNCER,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -115,7 +122,7 @@
             .distinctUntilChanged()
 
     fun dismissPrimaryBouncer() {
-        startTransitionTo(KeyguardState.GONE)
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
     }
 
     private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
@@ -124,7 +131,7 @@
                 .sample(
                     combine(
                         powerInteractor.isAwake,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
                         keyguardInteractor.isActiveDreamLockscreenHosted,
                         ::toQuad
@@ -158,7 +165,7 @@
                 .sample(
                     combine(
                         powerInteractor.isAsleep,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Triple
                     ),
@@ -185,7 +192,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isActiveDreamLockscreenHosted,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -213,7 +220,7 @@
 
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
                     if (
                         isKeyguardGoingAway &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index d5ac283..5c2df45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -24,8 +24,11 @@
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Each TransitionInteractor is responsible for determining under which conditions to notify
@@ -40,14 +43,25 @@
  */
 sealed class TransitionInteractor(
     val fromState: KeyguardState,
+    val transitionInteractor: KeyguardTransitionInteractor,
+    val mainDispatcher: CoroutineDispatcher,
+    val bgDispatcher: CoroutineDispatcher,
 ) {
     val name = this::class.simpleName ?: "UnknownTransitionInteractor"
-
     abstract val transitionRepository: KeyguardTransitionRepository
-    abstract val transitionInteractor: KeyguardTransitionInteractor
     abstract fun start()
 
-    fun startTransitionTo(
+    /* Use background dispatcher for all [KeyguardTransitionInteractor] flows. Necessary because
+     * the [sample] utility internally runs a collect on the Unconfined dispatcher, resulting
+     * in continuations on the main thread. We don't want that for classes that inherit from this.
+     */
+    val startedKeyguardTransitionStep =
+        transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher)
+    // The following are MutableSharedFlows, and do not require flowOn
+    val startedKeyguardState = transitionInteractor.startedKeyguardState
+    val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+
+    suspend fun startTransitionTo(
         toState: KeyguardState,
         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
         modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
@@ -67,16 +81,17 @@
             )
             return null
         }
-
-        return transitionRepository.startTransition(
-            TransitionInfo(
-                name,
-                fromState,
-                toState,
-                animator,
-                modeOnCanceled,
+        return withContext(mainDispatcher) {
+            transitionRepository.startTransition(
+                TransitionInfo(
+                    name,
+                    fromState,
+                    toState,
+                    animator,
+                    modeOnCanceled,
+                )
             )
-        )
+        }
     }
 
     /** This signal may come in before the occlusion signal, and can provide a custom transition */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 0385aeb..523414c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1153,7 +1153,7 @@
                 qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
                 onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
                 onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
-                // TODO(b/308813166): revisit logic once interactions between the hub and
+                // TODO(b/311234666): revisit logic once interactions between the hub and
                 //  shade/keyguard state are finalized
                 isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
                 onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index fc06090..fe10eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent
 import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -32,13 +34,23 @@
 interface QSTileIntentUserInputHandler {
 
     fun handle(view: View?, intent: Intent)
-    fun handle(view: View?, pendingIntent: PendingIntent)
+
+    /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
+    fun handle(
+        view: View?,
+        pendingIntent: PendingIntent,
+        requestLaunchingDefaultActivity: Boolean = false
+    )
 }
 
 @SysUISingleton
 class QSTileIntentUserInputHandlerImpl
 @Inject
-constructor(private val activityStarter: ActivityStarter) : QSTileIntentUserInputHandler {
+constructor(
+    private val activityStarter: ActivityStarter,
+    private val packageManager: PackageManager,
+    private val userHandle: UserHandle,
+) : QSTileIntentUserInputHandler {
 
     override fun handle(view: View?, intent: Intent) {
         val animationController: ActivityLaunchAnimator.Controller? =
@@ -52,21 +64,41 @@
     }
 
     // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
-    override fun handle(view: View?, pendingIntent: PendingIntent) {
-        if (!pendingIntent.isActivity) {
-            return
-        }
-        val animationController: ActivityLaunchAnimator.Controller? =
-            view?.let {
-                ActivityLaunchAnimator.Controller.fromView(
-                    it,
-                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+    override fun handle(
+        view: View?,
+        pendingIntent: PendingIntent,
+        requestLaunchingDefaultActivity: Boolean
+    ) {
+        if (pendingIntent.isActivity) {
+            val animationController: ActivityLaunchAnimator.Controller? =
+                view?.let {
+                    ActivityLaunchAnimator.Controller.fromView(
+                        it,
+                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+                    )
+                }
+            activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+        } else if (requestLaunchingDefaultActivity) {
+            val intent =
+                Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_LAUNCHER)
+                    .setPackage(pendingIntent.creatorPackage)
+                    .addFlags(
+                        Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                    )
+            val intents =
+                packageManager.queryIntentActivitiesAsUser(
+                    intent,
+                    PackageManager.ResolveInfoFlags.of(0L),
+                    userHandle.identifier
                 )
-            }
-        activityStarter.startPendingIntentMaybeDismissingKeyguard(
-            pendingIntent,
-            null,
-            animationController
-        )
+            intents
+                .firstOrNull { it.activityInfo.exported }
+                ?.let { resolved ->
+                    intent.setPackage(null)
+                    intent.setComponent(resolved.activityInfo.componentName)
+                    handle(view, intent)
+                }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
index afca57c..0ad520b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
@@ -18,9 +18,7 @@
 
 import android.content.Intent
 import android.provider.AlarmClock
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
@@ -31,34 +29,20 @@
 class AlarmTileUserActionInteractor
 @Inject
 constructor(
-    private val activityStarter: ActivityStarter,
+    private val inputHandler: QSTileIntentUserInputHandler,
 ) : QSTileUserActionInteractor<AlarmTileModel> {
     override suspend fun handleInput(input: QSTileInput<AlarmTileModel>): Unit =
         with(input) {
             when (action) {
                 is QSTileUserAction.Click -> {
-                    val animationController =
-                        action.view?.let {
-                            ActivityLaunchAnimator.Controller.fromView(
-                                it,
-                                InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
-                            )
-                        }
                     if (
                         data is AlarmTileModel.NextAlarmSet &&
                             data.alarmClockInfo.showIntent != null
                     ) {
                         val pendingIndent = data.alarmClockInfo.showIntent
-                        activityStarter.postStartActivityDismissingKeyguard(
-                            pendingIndent,
-                            animationController
-                        )
+                        inputHandler.handle(action.view, pendingIndent, true)
                     } else {
-                        activityStarter.postStartActivityDismissingKeyguard(
-                            Intent(AlarmClock.ACTION_SHOW_ALARMS),
-                            0,
-                            animationController
-                        )
+                        inputHandler.handle(action.view, Intent(AlarmClock.ACTION_SHOW_ALARMS))
                     }
                 }
                 is QSTileUserAction.LongClick -> {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 3be60b7..782d651 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.shade
 
 import android.content.Context
+import android.os.PowerManager
+import android.os.SystemClock
 import android.view.GestureDetector
 import android.view.MotionEvent
 import android.view.View
@@ -44,6 +46,7 @@
     private val communalViewModel: CommunalViewModel,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
+    private val powerManager: PowerManager,
 ) {
     /** The container view for the hub. This will not be initialized until [initView] is called. */
     private lateinit var communalContainerView: View
@@ -157,7 +160,7 @@
         // If the hub is fully visible, send all touch events to it.
         val communalVisible = hubShowing && !hubOccluded
         if (communalVisible) {
-            communalContainerView.dispatchTouchEvent(ev)
+            dispatchTouchEvent(ev)
             // Return true regardless of dispatch result as some touches at the start of a gesture
             // may return false from dispatchTouchEvent.
             return true
@@ -175,7 +178,7 @@
                 x >= communalContainerView.width - edgeSwipeRegionWidth
             if (inOpeningSwipeRegion && !hubOccluded) {
                 isTrackingOpenGesture = true
-                communalContainerView.dispatchTouchEvent(ev)
+                dispatchTouchEvent(ev)
                 // Return true regardless of dispatch result as some touches at the start of a
                 // gesture may return false from dispatchTouchEvent.
                 return true
@@ -184,7 +187,7 @@
             if (isUp || isCancel) {
                 isTrackingOpenGesture = false
             }
-            communalContainerView.dispatchTouchEvent(ev)
+            dispatchTouchEvent(ev)
             // Return true regardless of dispatch result as some touches at the start of a gesture
             // may return false from dispatchTouchEvent.
             return true
@@ -192,4 +195,17 @@
 
         return false
     }
+
+    /**
+     * Dispatches the touch event to the communal container and sends a user activity event to reset
+     * the screen timeout.
+     */
+    private fun dispatchTouchEvent(ev: MotionEvent) {
+        communalContainerView.dispatchTouchEvent(ev)
+        powerManager.userActivity(
+            SystemClock.uptimeMillis(),
+            PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+            0
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 2438298..7f8be1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll
 import com.android.systemui.plugins.ActivityStarter
@@ -888,6 +889,9 @@
                     isDraggingDown = false
                     isTrackpadReverseScroll = false
                     shadeRepository.setLegacyLockscreenShadeTracking(false)
+                    if (KeyguardShadeMigrationNssl.isEnabled) {
+                        return true
+                    }
                 } else {
                     stopDragging()
                     return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index 29d53fc..9f878b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.data
 
 import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
 import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
 import dagger.Module
@@ -24,6 +25,7 @@
     includes =
         [
             KeyguardStatusBarRepositoryModule::class,
+            RemoteInputRepositoryModule::class,
             StatusBarModeRepositoryModule::class,
             StatusBarPhoneDataLayerModule::class
         ]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
new file mode 100644
index 0000000..c0302bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.RemoteInputController
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Repository used for tracking the state of notification remote input (e.g. when the user presses
+ * "reply" on a notification and the keyboard opens).
+ */
+interface RemoteInputRepository {
+    /** Whether remote input is currently active for any notification. */
+    val isRemoteInputActive: Flow<Boolean>
+}
+
+@SysUISingleton
+class RemoteInputRepositoryImpl
+@Inject
+constructor(
+    private val notificationRemoteInputManager: NotificationRemoteInputManager,
+) : RemoteInputRepository {
+    override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
+        trySend(false) // initial value is false
+        val callback =
+            object : RemoteInputController.Callback {
+                override fun onRemoteInputActive(active: Boolean) {
+                    trySend(active)
+                }
+            }
+        notificationRemoteInputManager.addControllerCallback(callback)
+        awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
+    }
+}
+
+@Module
+interface RemoteInputRepositoryModule {
+    @Binds fun bindImpl(impl: RemoteInputRepositoryImpl): RemoteInputRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
new file mode 100644
index 0000000..68f727b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.data.repository.RemoteInputRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor used for business logic pertaining to the notification remote input (e.g. when the
+ * user presses "reply" on a notification and the keyboard opens).
+ */
+@SysUISingleton
+class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) {
+    /** Is remote input currently active for a notification? */
+    val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index abc04b8..a30c294 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -69,6 +69,7 @@
 import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -1957,18 +1958,34 @@
                     mView.dispatchDownEventToScroller(ev);
                 }
             }
-            boolean scrollerWantsIt = false;
-            if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
-                    && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
-                scrollerWantsIt = mView.onScrollTouch(ev);
-            }
             boolean horizontalSwipeWantsIt = false;
-            if (mLongPressedView == null && !mView.isBeingDragged()
-                    && !expandingNotification
-                    && !mView.getExpandedInThisMotion()
-                    && !onlyScrollingInThisMotion
-                    && !mView.getDisallowDismissInThisMotion()) {
-                horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+            boolean scrollerWantsIt = false;
+            if (KeyguardShadeMigrationNssl.isEnabled()) {
+                // Reverse the order relative to the else statement. onScrollTouch will reset on an
+                // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
+                if (mLongPressedView == null && !mView.isBeingDragged()
+                        && !expandingNotification
+                        && !mView.getExpandedInThisMotion()
+                        && !onlyScrollingInThisMotion
+                        && !mView.getDisallowDismissInThisMotion()) {
+                    horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+                }
+                if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+                        && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
+                    scrollerWantsIt = mView.onScrollTouch(ev);
+                }
+            } else {
+                if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+                        && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
+                    scrollerWantsIt = mView.onScrollTouch(ev);
+                }
+                if (mLongPressedView == null && !mView.isBeingDragged()
+                        && !expandingNotification
+                        && !mView.getExpandedInThisMotion()
+                        && !onlyScrollingInThisMotion
+                        && !mView.getDisallowDismissInThisMotion()) {
+                    horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+                }
             }
 
             // Check if we need to clear any snooze leavebehinds
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 8e81185..809947d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -63,6 +63,8 @@
                 transitionRepository = super.transitionRepository,
                 transitionInteractor = super.transitionInteractor,
                 scope = super.testScope.backgroundScope,
+                bgDispatcher = super.testDispatcher,
+                mainDispatcher = super.testDispatcher,
                 keyguardInteractor = super.keyguardInteractor,
                 flags = FakeFeatureFlags(),
                 keyguardSecurityModel = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index b8a8bdf..e531d44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.flags.FakeFeatureFlags
@@ -51,6 +50,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
@@ -109,7 +109,8 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScopeProvider.getTestScope()
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
 
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
@@ -139,6 +140,8 @@
         fromLockscreenTransitionInteractor =
             FromLockscreenTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -160,6 +163,8 @@
         fromPrimaryBouncerTransitionInteractor =
             FromPrimaryBouncerTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -173,6 +178,8 @@
         fromDreamingTransitionInteractor =
             FromDreamingTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -182,6 +189,8 @@
         fromDreamingLockscreenHostedTransitionInteractor =
             FromDreamingLockscreenHostedTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -191,6 +200,8 @@
         fromAodTransitionInteractor =
             FromAodTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -200,6 +211,8 @@
         fromGoneTransitionInteractor =
             FromGoneTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -210,6 +223,8 @@
         fromDozingTransitionInteractor =
             FromDozingTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -220,6 +235,8 @@
         fromOccludedTransitionInteractor =
             FromOccludedTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -230,6 +247,8 @@
         fromAlternateBouncerTransitionInteractor =
             FromAlternateBouncerTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = testDispatcher,
+                    mainDispatcher = testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 5569ca9..b7a9ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade
 
+import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -43,6 +44,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -52,6 +55,7 @@
     @Mock private lateinit var communalViewModel: CommunalViewModel
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var shadeInteractor: ShadeInteractor
+    @Mock private lateinit var powerManager: PowerManager
 
     private lateinit var containerView: View
     private lateinit var testableLooper: TestableLooper
@@ -76,7 +80,8 @@
                 communalInteractor,
                 communalViewModel,
                 keyguardTransitionInteractor,
-                shadeInteractor
+                shadeInteractor,
+                powerManager
             )
         testableLooper = TestableLooper.get(this)
 
@@ -90,14 +95,14 @@
     }
 
     @Test
-    fun isEnabled_interactorEnabled_returnsTrue() {
+    fun isEnabled_interactorEnabled_interceptsTouches() {
         communalRepository.setIsCommunalEnabled(true)
 
         assertThat(underTest.isEnabled()).isTrue()
     }
 
     @Test
-    fun isEnabled_interactorDisabled_returnsFalse() {
+    fun isEnabled_interactorDisabled_doesNotIntercept() {
         communalRepository.setIsCommunalEnabled(false)
 
         assertThat(underTest.isEnabled()).isFalse()
@@ -120,7 +125,7 @@
     }
 
     @Test
-    fun onTouchEvent_touchInsideGestureRegion_returnsTrue() {
+    fun onTouchEvent_touchInsideGestureRegion_interceptsTouches() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -131,7 +136,7 @@
     }
 
     @Test
-    fun onTouchEvent_subsequentTouchesAfterGestureStart_returnsTrue() {
+    fun onTouchEvent_subsequentTouchesAfterGestureStart_interceptsTouches() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -146,7 +151,7 @@
     }
 
     @Test
-    fun onTouchEvent_communalOpen_returnsTrue() {
+    fun onTouchEvent_communalOpen_interceptsTouches() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -155,10 +160,12 @@
 
         // Touch events are intercepted.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+        // User activity sent to PowerManager.
+        verify(powerManager).userActivity(any(), any(), any())
     }
 
     @Test
-    fun onTouchEvent_communalAndBouncerShowing_returnsFalse() {
+    fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -170,10 +177,12 @@
 
         // Touch events are not intercepted.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+        // User activity is not sent to PowerManager.
+        verify(powerManager, times(0)).userActivity(any(), any(), any())
     }
 
     @Test
-    fun onTouchEvent_communalAndShadeShowing_returnsFalse() {
+    fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 437d00a..5d663d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -216,6 +216,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mUtils.getTestDispatcher(),
+                mUtils.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
@@ -234,6 +236,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mUtils.getTestDispatcher(),
+                mUtils.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mKeyguardSecurityModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 39051eb..982787b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -251,6 +251,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mUtils.getTestDispatcher(),
+                mUtils.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mShadeRepository,
@@ -269,6 +271,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mUtils.getTestDispatcher(),
+                mUtils.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index aed6163..83590ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -79,6 +79,7 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
+    private val testDispatcher = utils.testDispatcher
     private lateinit var shadeInteractor: ShadeInteractor
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
     private lateinit var fromPrimaryBouncerTransitionInteractor:
@@ -143,6 +144,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 testScope.backgroundScope,
+                testDispatcher,
+                testDispatcher,
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
@@ -162,6 +165,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 testScope.backgroundScope,
+                testDispatcher,
+                testDispatcher,
                 keyguardInteractor,
                 featureFlags,
                 mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index b7529a8..048120a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -444,6 +444,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mUtils.getTestDispatcher(),
+                mUtils.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
@@ -462,6 +464,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mUtils.getTestDispatcher(),
+                mUtils.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
index e24ba26..c2dc673 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
@@ -48,6 +48,9 @@
     @get:[Provides Application]
     val appScope: CoroutineScope = scope.backgroundScope
 
+    @get:[Provides Background]
+    val bgScope: CoroutineScope = scope.backgroundScope
+
     @Module
     interface Bindings {
         @Binds @Main fun bindMainContext(dispatcher: TestDispatcher): CoroutineContext
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index b03d0b8..b1a0b67 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
 import dagger.Lazy
@@ -30,6 +31,8 @@
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             flags = featureFlagsClassic,
             shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index ade3e1a..97536e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
@@ -30,6 +31,8 @@
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             flags = featureFlagsClassic,
             keyguardSecurityModel = keyguardSecurityModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index 1185f2e..0307c41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -35,14 +35,21 @@
         mutableInputs.add(Input.Intent(view, intent))
     }
 
-    override fun handle(view: View?, pendingIntent: PendingIntent) {
-        mutableInputs.add(Input.PendingIntent(view, pendingIntent))
+    override fun handle(
+        view: View?,
+        pendingIntent: PendingIntent,
+        requestLaunchingDefaultActivity: Boolean
+    ) {
+        mutableInputs.add(Input.PendingIntent(view, pendingIntent, requestLaunchingDefaultActivity))
     }
 
     sealed interface Input {
         data class Intent(val view: View?, val intent: android.content.Intent) : Input
-        data class PendingIntent(val view: View?, val pendingIntent: android.app.PendingIntent) :
-            Input
+        data class PendingIntent(
+            val view: View?,
+            val pendingIntent: android.app.PendingIntent,
+            val requestLaunchingDefaultActivity: Boolean
+        ) : Input
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
new file mode 100644
index 0000000..c416ea1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.statusbar.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeRemoteInputRepository : RemoteInputRepository {
+    override val isRemoteInputActive = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt
new file mode 100644
index 0000000..1684efb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.remoteInputRepository: RemoteInputRepository by
+    Kosmos.Fixture { fakeRemoteInputRepository }
+val Kosmos.fakeRemoteInputRepository by Kosmos.Fixture { FakeRemoteInputRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt
new file mode 100644
index 0000000..07b39dc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.statusbar.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.remoteInputRepository
+
+val Kosmos.remoteInputInteractor by Kosmos.Fixture { RemoteInputInteractor(remoteInputRepository) }
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index e52cefb..81fd8ce 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,7 +39,4 @@
     sdk_version: "current",
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/packages/SystemUI/unfold/lint-baseline.xml b/packages/SystemUI/unfold/lint-baseline.xml
deleted file mode 100644
index 449ed2e..0000000
--- a/packages/SystemUI/unfold/lint-baseline.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
-</issues>
diff --git a/services/Android.bp b/services/Android.bp
index 0b484f4..7e8333c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -148,9 +148,6 @@
 java_library {
     name: "Slogf",
     srcs: ["core/java/com/android/server/utils/Slogf.java"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // merge all required services into one jar
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index a354671..69cc68a 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -22,6 +22,7 @@
     lint: {
         error_checks: ["MissingPermissionAnnotation"],
         baseline_filename: "lint-baseline.xml",
+
     },
     srcs: [
         ":services.accessibility-sources",
@@ -50,9 +51,6 @@
     libs: [
         "androidx.annotation_annotation",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 aconfig_declarations {
diff --git a/services/accessibility/lint-baseline.xml b/services/accessibility/lint-baseline.xml
index 6bec8cf..b808219 100644
--- a/services/accessibility/lint-baseline.xml
+++ b/services/accessibility/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="SimpleManualPermissionEnforcement"
@@ -23,4 +23,4 @@
             column="9"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/backup/lint-baseline.xml b/services/backup/lint-baseline.xml
index 93c9390..46de2cdd 100644
--- a/services/backup/lint-baseline.xml
+++ b/services/backup/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -36,4 +36,4 @@
             line="207"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 62c6703..3e45626 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -125,7 +125,7 @@
      * Send a message to remote devices through the transports
      */
     public void sendMessage(int message, byte[] data, int[] associationIds) {
-        Slog.i(TAG, "Sending message 0x" + Integer.toHexString(message)
+        Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
                 + " data length " + data.length);
         synchronized (mTransports) {
             for (int i = 0; i < associationIds.length; i++) {
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 22b18ac..8a5774e 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -284,7 +284,7 @@
         if (mListeners.containsKey(message)) {
             try {
                 mListeners.get(message).onMessageReceived(getAssociationId(), data);
-                Slog.i(TAG, "Message 0x" + Integer.toHexString(message)
+                Slog.d(TAG, "Message 0x" + Integer.toHexString(message)
                         + " is received from associationId " + mAssociationId
                         + ", sending data length " + data.length + " to the listener.");
             } catch (RemoteException ignored) {
diff --git a/services/companion/lint-baseline.xml b/services/companion/lint-baseline.xml
index 03eae39..020126f 100644
--- a/services/companion/lint-baseline.xml
+++ b/services/companion/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -12,4 +12,4 @@
             column="14"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a3fc3bf..a6ed498 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -234,9 +234,6 @@
 java_library {
     name: "services.core",
     static_libs: ["services.core.priorityboosted"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library_host {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 02f4485..a78bebd 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5453,7 +5453,7 @@
         // Force an immediate oomAdjUpdate, so the client app could be in the correct process state
         // before doing any service related transactions
         mAm.enqueueOomAdjTargetLocked(app);
-        mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
 
         boolean created = false;
         try {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index c2bc1e4..a30cdc4 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -62,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -147,6 +148,15 @@
 
     private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
 
+    // see {@link #recordToPersistedString(SoundDoseRecord)}
+    // this is computed conservatively to accommodate the legacy persisting of SoundDoseRecords in
+    // which we materialized more decimal values.
+    // TODO: adjust value after soaking in
+    private static final int MAX_RECORDS_STRING_LENGTH = 50;
+    private static final int MAX_SETTINGS_LENGTH = 32768;
+    private static final int MAX_NUMBER_OF_CACHED_RECORDS =
+            MAX_SETTINGS_LENGTH / MAX_RECORDS_STRING_LENGTH;
+
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
@@ -923,7 +933,7 @@
         Log.v(TAG, "Initializing sound dose");
 
         try {
-            if (mCachedAudioDeviceCategories.size() > 0) {
+            if (!mCachedAudioDeviceCategories.isEmpty()) {
                 soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray(
                         new ISoundDose.AudioDeviceCategory[0]));
                 mCachedAudioDeviceCategories.clear();
@@ -957,6 +967,7 @@
                         mGlobalTimeOffsetInSecs);
                 if (records != null) {
                     mDoseRecords.addAll(records);
+                    sanitizeDoseRecords_l();
                 }
             }
         }
@@ -1176,17 +1187,35 @@
                                 && r.duration == record.duration)) {
                     Log.w(TAG, "Could not find cached record to remove: " + record);
                 }
-            } else {
+            } else if (record.value > 0) {
                 mDoseRecords.add(record);
             }
         }
 
+        sanitizeDoseRecords_l();
+
         mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
                 /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
 
         mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
     }
 
+    @GuardedBy("mCsdStateLock")
+    private void sanitizeDoseRecords_l() {
+        if (mDoseRecords.size() > MAX_NUMBER_OF_CACHED_RECORDS) {
+            int nrToRemove = MAX_NUMBER_OF_CACHED_RECORDS - mDoseRecords.size();
+            Log.w(TAG,
+                    "Removing " + nrToRemove + " records from the total of " + mDoseRecords.size());
+            // Remove older elements to fit into persisted settings max length
+            Iterator<SoundDoseRecord> recordIterator = mDoseRecords.iterator();
+            while (recordIterator.hasNext() && nrToRemove > 0) {
+                recordIterator.next();
+                recordIterator.remove();
+                --nrToRemove;
+            }
+        }
+    }
+
     @SuppressWarnings("GuardedBy")  // avoid limitation with intra-procedural analysis of lambdas
     private void onPersistSoundDoseRecords() {
         synchronized (mCsdStateLock) {
@@ -1213,8 +1242,8 @@
             long globalTimeOffsetInSecs) {
         return convertToGlobalTime(record.timestamp, globalTimeOffsetInSecs)
                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
-                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.value
-                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.averageMel;
+                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.value)
+                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.averageMel);
     }
 
     private static long convertToGlobalTime(long bootTimeInSecs, long globalTimeOffsetInSecs) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index d02b6f4..171fbb6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.location.GnssMeasurement;
 import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.IGnssMeasurementsListener;
@@ -33,6 +34,7 @@
 import android.stats.location.LocationStatsEnums;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.location.gnss.GnssConfiguration.HalInterfaceVersion;
 import com.android.server.location.gnss.hal.GnssNative;
 import com.android.server.location.injector.AppOpsHelper;
@@ -40,6 +42,8 @@
 import com.android.server.location.injector.LocationUsageLogger;
 import com.android.server.location.injector.SettingsHelper;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Collection;
 
 /**
@@ -91,6 +95,9 @@
     private final LocationUsageLogger mLogger;
     private final GnssNative mGnssNative;
 
+    @GuardedBy("mMultiplexerLock")
+    private GnssMeasurementsEvent mLastGnssMeasurementsEvent;
+
     public GnssMeasurementsProvider(Injector injector, GnssNative gnssNative) {
         super(injector);
         mAppOpsHelper = injector.getAppOpsHelper();
@@ -264,5 +271,46 @@
                 return null;
             }
         });
+        synchronized (mMultiplexerLock) {
+            mLastGnssMeasurementsEvent = event;
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+        pw.print("last measurements=");
+        pw.println(getLastMeasurementEventSummary());
+    }
+
+    /**
+     * Returns a string of GnssMeasurementsEvent summary including received time, satellite count
+     * and average baseband C/No.
+     */
+    private String getLastMeasurementEventSummary() {
+        synchronized (mMultiplexerLock) {
+            if (mLastGnssMeasurementsEvent == null) {
+                return null;
+            }
+            StringBuilder builder = new StringBuilder("[");
+            builder.append("elapsedRealtimeNs=").append(
+                    mLastGnssMeasurementsEvent.getClock().getElapsedRealtimeNanos());
+            builder.append(" measurementCount=").append(
+                    mLastGnssMeasurementsEvent.getMeasurements().size());
+
+            float sumBasebandCn0 = 0;
+            int countBasebandCn0 = 0;
+            for (GnssMeasurement measurement : mLastGnssMeasurementsEvent.getMeasurements()) {
+                if (measurement.hasBasebandCn0DbHz()) {
+                    sumBasebandCn0 += measurement.getBasebandCn0DbHz();
+                    countBasebandCn0++;
+                }
+            }
+            if (countBasebandCn0 > 0) {
+                builder.append(" avgBasebandCn0=").append(sumBasebandCn0 / countBasebandCn0);
+            }
+            builder.append("]");
+            return builder.toString();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index ad09082..542b3b0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -52,6 +52,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
@@ -74,6 +75,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.sqlite.SQLiteDatabase;
@@ -303,7 +305,7 @@
     private boolean mThirdPartyAppsStarted;
 
     // Current password metrics for all secured users on the device. Updated when user unlocks the
-    // device or changes password. Removed when user is stopped.
+    // device or changes password. Removed if user is stopped with its CE key evicted.
     @GuardedBy("this")
     private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
     @VisibleForTesting
@@ -793,13 +795,33 @@
     }
 
     @VisibleForTesting
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     void onUserStopped(int userId) {
         hideEncryptionNotification(new UserHandle(userId));
-        // User is stopped with its CE key evicted. Restore strong auth requirement to the default
-        // flags after boot since stopping and restarting a user later is equivalent to rebooting
-        // the device.
+
+        // Normally, CE storage is locked when a user is stopped, and restarting the user requires
+        // strong auth.  Therefore, reset the user's strong auth flags.  The exception is users that
+        // allow delayed locking; under some circumstances, biometric authentication is allowed to
+        // restart such users.  Don't reset the strong auth flags for such users.
+        //
+        // TODO(b/319142556): It might make more sense to reset the strong auth flags when CE
+        // storage is locked, instead of when the user is stopped.  This would ensure the flags get
+        // reset if CE storage is locked later for a user that allows delayed locking.
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+            UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
+            if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
+                return;
+            }
+        }
         int strongAuthRequired = LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
         requireStrongAuth(strongAuthRequired, userId);
+
+        // Don't keep the password metrics in memory for a stopped user that will require strong
+        // auth to start again, since strong auth will make the password metrics available again.
         synchronized (this) {
             mUserPasswordMetrics.remove(userId);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7aa7b7e..9ddc362 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -215,7 +215,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -373,6 +372,7 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.time.Clock;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -2466,8 +2466,8 @@
         mMetricsLogger = new MetricsLogger();
         mRankingHandler = rankingHandler;
         mConditionProviders = conditionProviders;
-        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders,
-                flagResolver, new ZenModeEventLogger(mPackageManagerClient));
+        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), Clock.systemUTC(),
+                mConditionProviders, flagResolver, new ZenModeEventLogger(mPackageManagerClient));
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
             public void onConfigChanged() {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 911643b..afbf08d 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -117,6 +117,9 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -130,9 +133,12 @@
     static final String TAG = "ZenModeHelper";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private static final String PACKAGE_ANDROID = "android";
+
     // The amount of time rules instances can exist without their owning app being installed.
     private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
     static final int RULE_LIMIT_PER_PACKAGE = 100;
+    private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30);
 
     private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
 
@@ -148,6 +154,7 @@
 
     private final Context mContext;
     private final H mHandler;
+    private final Clock mClock;
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
     private final NotificationManager mNotificationManager;
@@ -189,11 +196,13 @@
 
     private String[] mPriorityOnlyDndExemptPackages;
 
-    public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders,
+    public ZenModeHelper(Context context, Looper looper, Clock clock,
+            ConditionProviders conditionProviders,
             SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
             ZenModeEventLogger zenModeEventLogger) {
         mContext = context;
         mHandler = new H(looper);
+        mClock = clock;
         addCallback(mMetrics);
         mAppOps = context.getSystemService(AppOpsManager.class);
         mNotificationManager = context.getSystemService(NotificationManager.class);
@@ -452,6 +461,7 @@
             newConfig = mConfig.copy();
             ZenRule rule = new ZenRule();
             populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
+            rule = maybeRestoreRemovedRule(newConfig, rule, automaticZenRule, origin);
             newConfig.automaticRules.put(rule.id, rule);
             maybeReplaceDefaultRule(newConfig, automaticZenRule);
 
@@ -463,6 +473,37 @@
         }
     }
 
+    private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
+            AutomaticZenRule azrToAdd, @ConfigChangeOrigin int origin) {
+        if (!Flags.modesApi()) {
+            return ruleToAdd;
+        }
+        String deletedKey = ZenModeConfig.deletedRuleKey(ruleToAdd);
+        if (deletedKey == null) {
+            // Couldn't calculate the deletedRuleKey (condition or pkg null?). This should
+            // never happen for an app-provided rule because NMS validates both.
+            return ruleToAdd;
+        }
+        ZenRule ruleToRestore = config.deletedRules.get(deletedKey);
+        if (ruleToRestore == null) {
+            return ruleToAdd; // Cannot restore.
+        }
+
+        // We have found a previous rule to maybe restore. Whether we do that or not, we don't need
+        // to keep it around (if not restored now, it won't be in future calls either).
+        config.deletedRules.remove(deletedKey);
+        ruleToRestore.deletionInstant = null;
+
+        if (origin != UPDATE_ORIGIN_APP) {
+            return ruleToAdd; // Okay to create anew.
+        }
+
+        // "Preserve" the previous rule by considering the azrToAdd an update instead.
+        // Only app-modifiable fields will actually be modified.
+        populateZenRule(ruleToRestore.pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+        return ruleToRestore;
+    }
+
     private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
         if (!Flags.modesApi()) {
             return;
@@ -644,7 +685,7 @@
         ZenRule rule = new ZenRule();
         rule.id = implicitRuleId(pkg);
         rule.pkg = pkg;
-        rule.creationTime = System.currentTimeMillis();
+        rule.creationTime = mClock.millis();
 
         Binder.withCleanCallingIdentity(() -> {
             try {
@@ -664,7 +705,7 @@
         rule.condition = null;
         rule.conditionId = new Uri.Builder()
                 .scheme(Condition.SCHEME)
-                .authority("android")
+                .authority(PACKAGE_ANDROID)
                 .appendPath("implicit")
                 .appendPath(pkg)
                 .build();
@@ -693,7 +734,9 @@
             if (ruleToRemove == null) return false;
             if (canManageAutomaticZenRule(ruleToRemove)) {
                 newConfig.automaticRules.remove(id);
-                if (ruleToRemove.getPkg() != null && !"android".equals(ruleToRemove.getPkg())) {
+                maybePreserveRemovedRule(newConfig, ruleToRemove, origin);
+                if (ruleToRemove.getPkg() != null
+                        && !PACKAGE_ANDROID.equals(ruleToRemove.getPkg())) {
                     for (ZenRule currRule : newConfig.automaticRules.values()) {
                         if (currRule.getPkg() != null
                                 && currRule.getPkg().equals(ruleToRemove.getPkg())) {
@@ -723,12 +766,44 @@
                 ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
                 if (Objects.equals(rule.getPkg(), packageName) && canManageAutomaticZenRule(rule)) {
                     newConfig.automaticRules.removeAt(i);
+                    maybePreserveRemovedRule(newConfig, rule, origin);
+                }
+            }
+            // If the system is clearing all rules this means DND access is revoked or the package
+            // was uninstalled, so also clear the preserved-deleted rules.
+            if (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI) {
+                for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
+                    ZenRule rule = newConfig.deletedRules.get(newConfig.deletedRules.keyAt(i));
+                    if (Objects.equals(rule.getPkg(), packageName)) {
+                        newConfig.deletedRules.removeAt(i);
+                    }
                 }
             }
             return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
         }
     }
 
+    private void maybePreserveRemovedRule(ZenModeConfig config, ZenRule ruleToRemove,
+            @ConfigChangeOrigin int origin) {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        // If an app deletes a previously customized rule, keep it around to preserve
+        // the user's customization when/if it's recreated later.
+        // We don't try to preserve system-owned rules because their conditionIds (used as
+        // deletedRuleKey) are not stable. This is almost moot anyway because an app cannot
+        // delete a system-owned rule.
+        if (origin == UPDATE_ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
+                && !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) {
+            String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove);
+            if (deletedKey != null) {
+                ruleToRemove.deletionInstant = Instant.now(mClock);
+                // Overwrites a previously-deleted rule with the same conditionId, but that's okay.
+                config.deletedRules.put(deletedKey, ruleToRemove);
+            }
+        }
+    }
+
     void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
             int callingUid) {
         ZenModeConfig newConfig;
@@ -919,7 +994,7 @@
             // These values can always be edited by the app, so we apply changes immediately.
             if (isNew) {
                 rule.id = ZenModeConfig.newRuleId();
-                rule.creationTime = System.currentTimeMillis();
+                rule.creationTime = mClock.millis();
                 rule.component = automaticZenRule.getOwner();
                 rule.pkg = pkg;
             }
@@ -1379,7 +1454,7 @@
             boolean hasDefaultRules = config.automaticRules.containsAll(
                     ZenModeConfig.DEFAULT_RULE_IDS);
 
-            long time = System.currentTimeMillis();
+            long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
             if (config.automaticRules != null && config.automaticRules.size() > 0) {
                 for (ZenRule automaticRule : config.automaticRules.values()) {
                     if (forRestore) {
@@ -1419,6 +1494,12 @@
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
             }
+
+            if (Flags.modesApi() && forRestore) {
+                // Note: forBackup doesn't write deletedRules, but just in case.
+                config.deletedRules.clear();
+            }
+
             if (DEBUG) Log.d(TAG, reason);
             synchronized (mConfigLock) {
                 setConfigLocked(config, null,
@@ -1436,7 +1517,7 @@
                 if (forBackup && mConfigs.keyAt(i) != userId) {
                     continue;
                 }
-                mConfigs.valueAt(i).writeXml(out, version);
+                mConfigs.valueAt(i).writeXml(out, version, forBackup);
             }
         }
     }
@@ -1468,28 +1549,51 @@
     }
 
     /**
-     * Removes old rule instances whose owner is not installed.
+     * Cleans up obsolete rules:
+     * <ul>
+     *     <li>Rule instances whose owner is not installed.
+     *     <li>Deleted rules that were deleted more than 30 days ago.
+     * </ul>
      */
     private void cleanUpZenRules() {
-        long currentTime = System.currentTimeMillis();
+        Instant keptRuleThreshold = mClock.instant().minus(DELETED_RULE_KEPT_FOR);
         synchronized (mConfigLock) {
             final ZenModeConfig newConfig = mConfig.copy();
-            if (newConfig.automaticRules != null) {
-                for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
-                    ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
-                    if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
-                        try {
-                            if (rule.getPkg() != null) {
-                                mPm.getPackageInfo(rule.getPkg(), PackageManager.MATCH_ANY_USER);
-                            }
-                        } catch (PackageManager.NameNotFoundException e) {
-                            newConfig.automaticRules.removeAt(i);
-                        }
+
+            deleteRulesWithoutOwner(newConfig.automaticRules);
+            if (Flags.modesApi()) {
+                deleteRulesWithoutOwner(newConfig.deletedRules);
+                for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
+                    ZenRule deletedRule = newConfig.deletedRules.valueAt(i);
+                    if (deletedRule.deletionInstant == null
+                            || deletedRule.deletionInstant.isBefore(keptRuleThreshold)) {
+                        newConfig.deletedRules.removeAt(i);
                     }
                 }
             }
-            setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules",
-                    Process.SYSTEM_UID);
+
+            if (!newConfig.equals(mConfig)) {
+                setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                        "cleanUpZenRules", Process.SYSTEM_UID);
+            }
+        }
+    }
+
+    private void deleteRulesWithoutOwner(ArrayMap<String, ZenRule> ruleList) {
+        long currentTime = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
+        if (ruleList != null) {
+            for (int i = ruleList.size() - 1; i >= 0; i--) {
+                ZenRule rule = ruleList.valueAt(i);
+                if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
+                    try {
+                        if (rule.getPkg() != null) {
+                            mPm.getPackageInfo(rule.getPkg(), PackageManager.MATCH_ANY_USER);
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        ruleList.removeAt(i);
+                    }
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
deleted file mode 100644
index 4454601..0000000
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
+++ /dev/null
@@ -1,97 +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.server.pm;
-
-import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-
-import android.annotation.NonNull;
-import android.app.BackgroundInstallControlManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IRemoteCallback;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.ServiceThread;
-
-public class BackgroundInstallControlCallbackHelper {
-
-    @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
-    @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
-    private static final String TAG = "BackgroundInstallControlCallbackHelper";
-
-    private final Handler mHandler;
-
-    BackgroundInstallControlCallbackHelper() {
-        HandlerThread backgroundThread =
-                new ServiceThread(
-                        "BackgroundInstallControlCallbackHelperBg",
-                        THREAD_PRIORITY_BACKGROUND,
-                        true);
-        backgroundThread.start();
-        mHandler = new Handler(backgroundThread.getLooper());
-    }
-
-    @NonNull @VisibleForTesting
-    final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
-
-    /** Registers callback that gets invoked upon detection of an MBA
-     *
-     * NOTE: The callback is user context agnostic and currently broadcasts to all users of other
-     * users app installs. This is fine because the API is for SystemServer use only.
-     */
-    public void registerBackgroundInstallCallback(IRemoteCallback callback) {
-        synchronized (mCallbacks) {
-            mCallbacks.register(callback, null);
-        }
-    }
-
-    /** Unregisters callback */
-    public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
-        synchronized (mCallbacks) {
-            mCallbacks.unregister(callback);
-        }
-    }
-
-    /**
-     * Invokes all registered callbacks Callbacks are processed through user provided-threads and
-     * parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent
-     */
-    public void notifyAllCallbacks(int userId, String packageName) {
-        Bundle extras = new Bundle();
-        extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName);
-        extras.putInt(FLAGGED_USER_ID_KEY, userId);
-        synchronized (mCallbacks) {
-            mHandler.post(
-                    () ->
-                            mCallbacks.broadcast(
-                                    callback -> {
-                                        try {
-                                            callback.sendResult(extras);
-                                        } catch (RemoteException e) {
-                                            Slog.e(
-                                                    TAG,
-                                                    "error detected: " + e.getLocalizedMessage(),
-                                                    e);
-                                        }
-                                    }));
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 3a9dedc..7f0aadc 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -16,12 +16,7 @@
 
 package com.android.server.pm;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
@@ -35,7 +30,6 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
@@ -75,10 +69,8 @@
     private static final String DISK_FILE_NAME = "states";
     private static final String DISK_DIR_NAME = "bic";
 
-    private static final String ENFORCE_PERMISSION_ERROR_MSG =
-            "User is not permitted to call service: ";
-
     private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
+
     private static final int MSG_USAGE_EVENT_RECEIVED = 0;
     private static final int MSG_PACKAGE_ADDED = 1;
     private static final int MSG_PACKAGE_REMOVED = 2;
@@ -86,20 +78,19 @@
     private final Context mContext;
     private final BinderService mBinderService;
     private final PackageManager mPackageManager;
-    // TODO migrate all internal PackageManager calls to PackageManagerInternal where possible.
-    // b/310983905
     private final PackageManagerInternal mPackageManagerInternal;
     private final UsageStatsManagerInternal mUsageStatsManagerInternal;
     private final PermissionManagerServiceInternal mPermissionManager;
     private final Handler mHandler;
     private final File mDiskFile;
 
+
     private SparseSetArray<String> mBackgroundInstalledPackages = null;
-    private final BackgroundInstallControlCallbackHelper mCallbackHelper;
 
     // User ID -> package name -> set of foreground time frame
-    private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>>
-            mInstallerForegroundTimeFrames = new SparseArrayMap<>();
+    private final SparseArrayMap<String,
+            TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
+            new SparseArrayMap<>();
 
     public BackgroundInstallControlService(@NonNull Context context) {
         this(new InjectorImpl(context));
@@ -115,11 +106,13 @@
         mHandler = new EventHandler(injector.getLooper(), this);
         mDiskFile = injector.getDiskFile();
         mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
-        mCallbackHelper = injector.getBackgroundInstallControlCallbackHelper();
         mUsageStatsManagerInternal.registerListener(
                 (userId, event) ->
-                        mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, userId, 0, event)
-                                .sendToTarget());
+                        mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
+                                userId,
+                                0,
+                                event).sendToTarget()
+        );
         mBinderService = new BinderService(this);
     }
 
@@ -133,15 +126,12 @@
         @Override
         public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
                 @PackageManager.PackageInfoFlagsBits long flags, int userId) {
-            mService.enforceCallerQueryPackagesPermissions();
             if (!Build.IS_DEBUGGABLE) {
                 return mService.getBackgroundInstalledPackages(flags, userId);
             }
             // The debug.transparency.bg-install-apps (only works for debuggable builds)
             // is used to set mock list of background installed apps for testing.
             // The list of apps' names is delimited by ",".
-            // TODO: Remove after migrating test to new background install method using
-            // {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905
             String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
             if (TextUtils.isEmpty(propertyString)) {
                 return mService.getBackgroundInstalledPackages(flags, userId);
@@ -149,41 +139,16 @@
                 return mService.getMockBackgroundInstalledPackages(propertyString);
             }
         }
-
-        @Override
-        public void registerBackgroundInstallCallback(IRemoteCallback callback) {
-            mService.enforceCallerQueryPackagesPermissions();
-            mService.enforceCallerInteractCrossUserPermissions();
-            mService.mCallbackHelper.registerBackgroundInstallCallback(callback);
-        }
-
-        @Override
-        public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
-            mService.enforceCallerQueryPackagesPermissions();
-            mService.enforceCallerInteractCrossUserPermissions();
-            mService.mCallbackHelper.unregisterBackgroundInstallCallback(callback);
-        }
-    }
-
-    @RequiresPermission(QUERY_ALL_PACKAGES)
-    void enforceCallerQueryPackagesPermissions() throws SecurityException {
-        mContext.enforceCallingPermission(QUERY_ALL_PACKAGES,
-                ENFORCE_PERMISSION_ERROR_MSG + QUERY_ALL_PACKAGES);
-    }
-
-    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
-    void enforceCallerInteractCrossUserPermissions() throws SecurityException {
-        mContext.enforceCallingPermission(INTERACT_ACROSS_USERS_FULL,
-                ENFORCE_PERMISSION_ERROR_MSG + INTERACT_ACROSS_USERS_FULL);
     }
 
     @VisibleForTesting
     ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
             @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                PackageManager.PackageInfoFlags.of(flags), userId);
+                    PackageManager.PackageInfoFlags.of(flags), userId);
 
         initBackgroundInstalledPackages();
+
         ListIterator<PackageInfo> iter = packages.listIterator();
         while (iter.hasNext()) {
             String packageName = iter.next().packageName;
@@ -205,9 +170,8 @@
         List<PackageInfo> mockPackages = new ArrayList<>();
         for (String name : mockPackageNames) {
             try {
-                PackageInfo packageInfo =
-                        mPackageManager.getPackageInfo(
-                                name, PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
+                PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
+                        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
                 mockPackages.add(packageInfo);
             } catch (PackageManager.NameNotFoundException e) {
                 Slog.w(TAG, "Package's PackageInfo not found " + name);
@@ -228,16 +192,18 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_USAGE_EVENT_RECEIVED:
-                    mService.handleUsageEvent(
-                            (UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
+                case MSG_USAGE_EVENT_RECEIVED: {
+                    mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
                     break;
-                case MSG_PACKAGE_ADDED:
+                }
+                case MSG_PACKAGE_ADDED: {
                     mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
                     break;
-                case MSG_PACKAGE_REMOVED:
+                }
+                case MSG_PACKAGE_REMOVED: {
                     mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
                     break;
+                }
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -247,9 +213,8 @@
     void handlePackageAdd(String packageName, int userId) {
         ApplicationInfo appInfo = null;
         try {
-            appInfo =
-                    mPackageManager.getApplicationInfoAsUser(
-                            packageName, PackageManager.ApplicationInfoFlags.of(0), userId);
+            appInfo = mPackageManager.getApplicationInfoAsUser(packageName,
+                    PackageManager.ApplicationInfoFlags.of(0), userId);
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Package's appInfo not found " + packageName);
             return;
@@ -268,18 +233,15 @@
 
         // the installers without INSTALL_PACKAGES perm can't perform
         // the installation in background. So we can just filter out them.
-        if (mPermissionManager.checkPermission(
-                installerPackageName,
-                android.Manifest.permission.INSTALL_PACKAGES,
-                Context.DEVICE_ID_DEFAULT,
-                userId)
-                != PERMISSION_GRANTED) {
+        if (mPermissionManager.checkPermission(installerPackageName,
+                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
+                userId) != PackageManager.PERMISSION_GRANTED) {
             return;
         }
 
         // convert up-time to current time.
-        final long installTimestamp =
-                System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+        final long installTimestamp = System.currentTimeMillis()
+                - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
 
         if (installedByAdb(initiatingPackageName)
                 || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
@@ -288,7 +250,6 @@
 
         initBackgroundInstalledPackages();
         mBackgroundInstalledPackages.add(userId, packageName);
-        mCallbackHelper.notifyAllCallbacks(userId, packageName);
         writeBackgroundInstalledPackagesToDisk();
     }
 
@@ -298,8 +259,8 @@
         return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
     }
 
-    private boolean wasForegroundInstallation(
-            String installerPackageName, int userId, long installTimestamp) {
+    private boolean wasForegroundInstallation(String installerPackageName,
+            int userId, long installTimestamp) {
         TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
                 mInstallerForegroundTimeFrames.get(userId, installerPackageName);
 
@@ -388,12 +349,12 @@
             for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
                 int userId = mBackgroundInstalledPackages.keyAt(i);
                 for (String packageName : mBackgroundInstalledPackages.get(userId)) {
-                    long token =
-                            protoOutputStream.start(
-                                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                    long token = protoOutputStream.start(
+                            BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                     protoOutputStream.write(
                             BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
-                    protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, userId + 1);
+                    protoOutputStream.write(
+                            BackgroundInstalledPackageProto.USER_ID, userId + 1);
                     protoOutputStream.end(token);
                 }
             }
@@ -426,28 +387,23 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token =
-                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName =
-                                    protoInputStream.readString(
-                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId =
-                                    protoInputStream.readInt(
-                                            BackgroundInstalledPackageProto.USER_ID)
-                                            - 1;
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
                             break;
                         default:
-                            Slog.w(
-                                    TAG,
-                                    "Undefined field in proto: "
-                                            + protoInputStream.getFieldNumber());
+                            Slog.w(TAG, "Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -476,12 +432,9 @@
         if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
             return true;
         }
-        return mPermissionManager.checkPermission(
-                pkgName,
-                android.Manifest.permission.INSTALL_PACKAGES,
-                Context.DEVICE_ID_DEFAULT,
-                userId)
-                == PERMISSION_GRANTED;
+        return mPermissionManager.checkPermission(pkgName,
+                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
+                userId) == PackageManager.PERMISSION_GRANTED;
     }
 
     @Override
@@ -495,22 +448,21 @@
             publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
         }
 
-        mPackageManagerInternal.getPackageList(
-                new PackageManagerInternal.PackageListObserver() {
-                    @Override
-                    public void onPackageAdded(String packageName, int uid) {
-                        final int userId = UserHandle.getUserId(uid);
-                        mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
-                                .sendToTarget();
-                    }
+        mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                final int userId = UserHandle.getUserId(uid);
+                mHandler.obtainMessage(MSG_PACKAGE_ADDED,
+                        userId, 0, packageName).sendToTarget();
+            }
 
-                    @Override
-                    public void onPackageRemoved(String packageName, int uid) {
-                        final int userId = UserHandle.getUserId(uid);
-                        mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
-                                .sendToTarget();
-                    }
-                });
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                final int userId = UserHandle.getUserId(uid);
+                mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
+                        userId, 0, packageName).sendToTarget();
+            }
+        });
     }
 
     // The foreground time frame (ForegroundTimeFrame) represents the period
@@ -566,7 +518,7 @@
     }
 
     /**
-     * Dependency injector for {@link BackgroundInstallControlService}.
+     * Dependency injector for {@link #BackgroundInstallControlService)}.
      */
     interface Injector {
         Context getContext();
@@ -582,8 +534,6 @@
         Looper getLooper();
 
         File getDiskFile();
-
-        BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper();
     }
 
     private static final class InjectorImpl implements Injector {
@@ -620,11 +570,11 @@
 
         @Override
         public Looper getLooper() {
-            ServiceThread serviceThread =
-                    new ServiceThread(
-                            TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+            ServiceThread serviceThread = new ServiceThread(TAG,
+                    android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
             serviceThread.start();
             return serviceThread.getLooper();
+
         }
 
         @Override
@@ -633,10 +583,5 @@
             File file = new File(dir, DISK_FILE_NAME);
             return file;
         }
-
-        @Override
-        public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
-            return new BackgroundInstallControlCallbackHelper();
-        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3adeb4b..446c629 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -77,6 +77,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.Looper;
@@ -113,7 +114,6 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -485,7 +485,14 @@
     }
 
     public ShortcutService(Context context) {
-        this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
+        this(context, getBgLooper(), /*onyForPackgeManagerApis*/ false);
+    }
+
+    private static Looper getBgLooper() {
+        final HandlerThread handlerThread = new HandlerThread("shortcut",
+                android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        handlerThread.start();
+        return handlerThread.getLooper();
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 6ce8685..ed04e5f 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -34,6 +34,7 @@
     @NonNull private final Looper mLooper;
     @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
     @NonNull private final FeatureFlags mFeatureFlags;
+    @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
     private final boolean mIsInTestMode;
 
     public VcnContext(
@@ -48,6 +49,7 @@
 
         // Auto-generated class
         mFeatureFlags = new FeatureFlagsImpl();
+        mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();
     }
 
     @NonNull
@@ -69,6 +71,14 @@
         return mIsInTestMode;
     }
 
+    public boolean isFlagNetworkMetricMonitorEnabled() {
+        return mFeatureFlags.networkMetricMonitor();
+    }
+
+    public boolean isFlagIpSecTransformStateEnabled() {
+        return mCoreNetFeatureFlags.ipsecTransformState();
+    }
+
     @NonNull
     public FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
new file mode 100644
index 0000000..5f4852f
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -0,0 +1,387 @@
+/*
+ * 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.server.vcn.routeselection;
+
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.net.vcn.VcnManager;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * IpSecPacketLossDetector is responsible for continuously monitoring IPsec packet loss
+ *
+ * <p>When the packet loss rate surpass the threshold, IpSecPacketLossDetector will report it to the
+ * caller
+ *
+ * <p>IpSecPacketLossDetector will start monitoring when the network being monitored is selected AND
+ * an inbound IpSecTransform has been applied to this network.
+ *
+ * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state"
+ */
+public class IpSecPacketLossDetector extends NetworkMetricMonitor {
+    private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PACKET_LOSS_UNAVALAIBLE = -1;
+
+    // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
+    // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
+    // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per
+    // "ICTP-SDU: About PingER"). Thus choose 12% as a conservative default threshold to declare a
+    // validation failure.
+    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12;
+
+    private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
+
+    private long mPollIpSecStateIntervalMs;
+    private final int mPacketLossRatePercentThreshold;
+
+    @NonNull private final Handler mHandler;
+    @NonNull private final PowerManager mPowerManager;
+    @NonNull private final Object mCancellationToken = new Object();
+    @NonNull private final PacketLossCalculator mPacketLossCalculator;
+
+    @Nullable private IpSecTransformWrapper mInboundTransform;
+    @Nullable private IpSecTransformState mLastIpSecTransformState;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback,
+            @NonNull Dependencies deps)
+            throws IllegalAccessException {
+        super(vcnContext, network, carrierConfig, callback);
+
+        Objects.requireNonNull(deps, "Missing deps");
+
+        if (!vcnContext.isFlagIpSecTransformStateEnabled()) {
+            // Caller error
+            logWtf("ipsecTransformState flag disabled");
+            throw new IllegalAccessException("ipsecTransformState flag disabled");
+        }
+
+        mHandler = new Handler(getVcnContext().getLooper());
+
+        mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
+
+        mPacketLossCalculator = deps.getPacketLossCalculator();
+
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+
+        // Register for system broadcasts to monitor idle mode change
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        getVcnContext()
+                .getContext()
+                .registerReceiver(
+                        new BroadcastReceiver() {
+                            @Override
+                            public void onReceive(Context context, Intent intent) {
+                                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+                                                intent.getAction())
+                                        && mPowerManager.isDeviceIdleMode()) {
+                                    mLastIpSecTransformState = null;
+                                }
+                            }
+                        },
+                        intentFilter,
+                        null /* broadcastPermission not required */,
+                        mHandler);
+    }
+
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        this(vcnContext, network, carrierConfig, callback, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        public PacketLossCalculator getPacketLossCalculator() {
+            return new PacketLossCalculator();
+        }
+    }
+
+    private static long getPollIpSecStateIntervalMs(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        final int seconds;
+
+        if (carrierConfig != null) {
+            seconds =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                            POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT);
+        } else {
+            seconds = POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT;
+        }
+
+        return TimeUnit.SECONDS.toMillis(seconds);
+    }
+
+    private static int getPacketLossRatePercentThreshold(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                    IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT);
+        }
+        return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
+    }
+
+    @Override
+    protected void onSelectedUnderlyingNetworkChanged() {
+        if (!isSelectedUnderlyingNetwork()) {
+            mInboundTransform = null;
+            stop();
+        }
+
+        // No action when the underlying network got selected. Wait for the inbound transform to
+        // start the monitor
+    }
+
+    @Override
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inboundTransform) {
+        Objects.requireNonNull(inboundTransform, "inboundTransform is null");
+
+        if (Objects.equals(inboundTransform, mInboundTransform)) {
+            return;
+        }
+
+        if (!isSelectedUnderlyingNetwork()) {
+            logWtf("setInboundTransform called but network not selected");
+            return;
+        }
+
+        // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
+        // enabled on the last one as a sample
+        mInboundTransform = inboundTransform;
+        start();
+    }
+
+    @Override
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new interval
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+    }
+
+    @Override
+    protected void start() {
+        super.start();
+        clearTransformStateAndPollingEvents();
+        mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        clearTransformStateAndPollingEvents();
+    }
+
+    private void clearTransformStateAndPollingEvents() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        mLastIpSecTransformState = null;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+
+        if (mInboundTransform != null) {
+            mInboundTransform.close();
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @Nullable
+    public IpSecTransformState getLastTransformState() {
+        return mLastIpSecTransformState;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    @Nullable
+    public IpSecTransformWrapper getInboundTransformInternal() {
+        return mInboundTransform;
+    }
+
+    private class PollIpSecStateRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!isStarted()) {
+                logWtf("Monitor stopped but PollIpSecStateRunnable not removed from Handler");
+                return;
+            }
+
+            getInboundTransformInternal()
+                    .getIpSecTransformState(
+                            new HandlerExecutor(mHandler), new IpSecTransformStateReceiver());
+
+            // Schedule for next poll
+            mHandler.postDelayed(
+                    new PollIpSecStateRunnable(), mCancellationToken, mPollIpSecStateIntervalMs);
+        }
+    }
+
+    private class IpSecTransformStateReceiver
+            implements OutcomeReceiver<IpSecTransformState, RuntimeException> {
+        @Override
+        public void onResult(@NonNull IpSecTransformState state) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            if (!isStarted()) {
+                return;
+            }
+
+            onIpSecTransformStateReceived(state);
+        }
+
+        @Override
+        public void onError(@NonNull RuntimeException error) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            // Nothing we can do here
+            logW("TransformStateReceiver#onError " + error.toString());
+        }
+    }
+
+    private void onIpSecTransformStateReceived(@NonNull IpSecTransformState state) {
+        if (mLastIpSecTransformState == null) {
+            // This is first time to poll the state
+            mLastIpSecTransformState = state;
+            return;
+        }
+
+        final int packetLossRate =
+                mPacketLossCalculator.getPacketLossRatePercentage(
+                        mLastIpSecTransformState, state, getLogPrefix());
+
+        if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+            return;
+        }
+
+        final String logMsg =
+                "packetLossRate: "
+                        + packetLossRate
+                        + "% in the past "
+                        + (state.getTimestamp() - mLastIpSecTransformState.getTimestamp())
+                        + "ms";
+
+        mLastIpSecTransformState = state;
+        if (packetLossRate < mPacketLossRatePercentThreshold) {
+            logV(logMsg);
+            onValidationResultReceivedInternal(false /* isFailed */);
+        } else {
+            logInfo(logMsg);
+            onValidationResultReceivedInternal(true /* isFailed */);
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class PacketLossCalculator {
+        /** Calculate the packet loss rate between two timestamps */
+        public int getPacketLossRatePercentage(
+                @NonNull IpSecTransformState oldState,
+                @NonNull IpSecTransformState newState,
+                String logPrefix) {
+            logVIpSecTransform("oldState", oldState, logPrefix);
+            logVIpSecTransform("newState", newState, logPrefix);
+
+            final int replayWindowSize = oldState.getReplayBitmap().length * 8;
+            final long oldSeqHi = oldState.getRxHighestSequenceNumber();
+            final long oldSeqLow = Math.max(0L, oldSeqHi - replayWindowSize + 1);
+            final long newSeqHi = newState.getRxHighestSequenceNumber();
+            final long newSeqLow = Math.max(0L, newSeqHi - replayWindowSize + 1);
+
+            if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
+                // The replay window did not proceed and all packets might have been delivered out
+                // of order
+                return PACKET_LOSS_UNAVALAIBLE;
+            }
+
+            // Get the expected packet count by assuming there is no packet loss. In this case, SA
+            // should receive all packets whose sequence numbers are smaller than the lower bound of
+            // the replay window AND the packets received within the window.
+            // When the lower bound is 0, it's not possible to tell whether packet with seqNo 0 is
+            // received or not. For simplicity just assume that packet is received.
+            final long newExpectedPktCnt = newSeqLow + getPacketCntInReplayWindow(newState);
+            final long oldExpectedPktCnt = oldSeqLow + getPacketCntInReplayWindow(oldState);
+
+            final long expectedPktCntDiff = newExpectedPktCnt - oldExpectedPktCnt;
+            final long actualPktCntDiff = newState.getPacketCount() - oldState.getPacketCount();
+
+            logV(
+                    TAG,
+                    logPrefix
+                            + " expectedPktCntDiff: "
+                            + expectedPktCntDiff
+                            + " actualPktCntDiff: "
+                            + actualPktCntDiff);
+
+            if (expectedPktCntDiff < 0
+                    || expectedPktCntDiff == 0
+                    || actualPktCntDiff < 0
+                    || actualPktCntDiff > expectedPktCntDiff) {
+                logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
+                return PACKET_LOSS_UNAVALAIBLE;
+            }
+
+            return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+        }
+    }
+
+    private static void logVIpSecTransform(
+            String transformTag, IpSecTransformState state, String logPrefix) {
+        final String stateString =
+                " seqNo: "
+                        + state.getRxHighestSequenceNumber()
+                        + " | pktCnt: "
+                        + state.getPacketCount()
+                        + " | pktCntInWindow: "
+                        + getPacketCntInReplayWindow(state);
+        logV(TAG, logPrefix + " " + transformTag + stateString);
+    }
+
+    /** Get the number of received packets within the replay window */
+    private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
+        return BitSet.valueOf(state.getReplayBitmap()).cardinality();
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
new file mode 100644
index 0000000..a79f188
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -0,0 +1,269 @@
+/*
+ * 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.server.vcn.routeselection;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.os.OutcomeReceiver;
+import android.util.CloseGuard;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * NetworkMetricMonitor is responsible for managing metric monitoring and tracking validation
+ * results.
+ *
+ * <p>This class is flag gated by "network_metric_monitor"
+ */
+public abstract class NetworkMetricMonitor implements AutoCloseable {
+    private static final String TAG = NetworkMetricMonitor.class.getSimpleName();
+
+    private static final boolean VDBG = false; // STOPSHIP: if true
+
+    @NonNull private final CloseGuard mCloseGuard = new CloseGuard();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Network mNetwork;
+    @NonNull private final NetworkMetricMonitorCallback mCallback;
+
+    private boolean mIsSelectedUnderlyingNetwork;
+    private boolean mIsStarted;
+    private boolean mIsValidationFailed;
+
+    protected NetworkMetricMonitor(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        if (!vcnContext.isFlagNetworkMetricMonitorEnabled()) {
+            // Caller error
+            logWtf("networkMetricMonitor flag disabled");
+            throw new IllegalAccessException("networkMetricMonitor flag disabled");
+        }
+
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mNetwork = Objects.requireNonNull(network, "Missing network");
+        mCallback = Objects.requireNonNull(callback, "Missing callback");
+
+        mIsSelectedUnderlyingNetwork = false;
+        mIsStarted = false;
+        mIsValidationFailed = false;
+    }
+
+    /** Callback to notify caller of the validation result */
+    public interface NetworkMetricMonitorCallback {
+        /** Called when there is a validation result is ready */
+        void onValidationResultReceived();
+    }
+
+    /**
+     * Start monitoring
+     *
+     * <p>This method might be called on a an already started monitor for updating monitor
+     * properties (e.g. IpSecTransform, carrier config)
+     *
+     * <p>Subclasses MUST call super.start() when overriding this method
+     */
+    protected void start() {
+        mIsStarted = true;
+    }
+
+    /**
+     * Stop monitoring
+     *
+     * <p>Subclasses MUST call super.stop() when overriding this method
+     */
+    public void stop() {
+        mIsValidationFailed = false;
+        mIsStarted = false;
+    }
+
+    /** Called by the subclasses when the validation result is ready */
+    protected void onValidationResultReceivedInternal(boolean isFailed) {
+        mIsValidationFailed = isFailed;
+        mCallback.onValidationResultReceived();
+    }
+
+    /** Called when the underlying network changes to selected or unselected */
+    protected abstract void onSelectedUnderlyingNetworkChanged();
+
+    /**
+     * Mark the network being monitored selected or unselected
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    public void setIsSelectedUnderlyingNetwork(boolean isSelectedUnderlyingNetwork) {
+        if (mIsSelectedUnderlyingNetwork == isSelectedUnderlyingNetwork) {
+            return;
+        }
+
+        mIsSelectedUnderlyingNetwork = isSelectedUnderlyingNetwork;
+        onSelectedUnderlyingNetworkChanged();
+    }
+
+    /** Wrapper that allows injection for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static class IpSecTransformWrapper {
+        @NonNull public final IpSecTransform ipSecTransform;
+
+        public IpSecTransformWrapper(@NonNull IpSecTransform ipSecTransform) {
+            this.ipSecTransform = ipSecTransform;
+        }
+
+        /** Poll an IpSecTransformState */
+        public void getIpSecTransformState(
+                @NonNull Executor executor,
+                @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
+            ipSecTransform.getIpSecTransformState(executor, callback);
+        }
+
+        /** Close this instance and release the underlying resources */
+        public void close() {
+            ipSecTransform.close();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ipSecTransform);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof IpSecTransformWrapper)) {
+                return false;
+            }
+
+            final IpSecTransformWrapper other = (IpSecTransformWrapper) o;
+
+            return Objects.equals(ipSecTransform, other.ipSecTransform);
+        }
+    }
+
+    /** Set the IpSecTransform that applied to the Network being monitored */
+    public void setInboundTransform(@NonNull IpSecTransform inTransform) {
+        setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
+    }
+
+    /**
+     * Set the IpSecTransform that applied to the Network being monitored *
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inTransform) {
+        // Subclasses MUST override it if they care
+    }
+
+    /** Update the carrierconfig */
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // Subclasses MUST override it if they care
+    }
+
+    public boolean isValidationFailed() {
+        return mIsValidationFailed;
+    }
+
+    public boolean isSelectedUnderlyingNetwork() {
+        return mIsSelectedUnderlyingNetwork;
+    }
+
+    public boolean isStarted() {
+        return mIsStarted;
+    }
+
+    @NonNull
+    public VcnContext getVcnContext() {
+        return mVcnContext;
+    }
+
+    // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method
+    @Override
+    public void close() {
+        mCloseGuard.close();
+
+        stop();
+    }
+
+    // Override #finalize() to use closeGuard for flagging that #close() was not called
+    @SuppressWarnings("Finalize")
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private String getClassName() {
+        return this.getClass().getSimpleName();
+    }
+
+    protected String getLogPrefix() {
+        return " [Network " + mNetwork + "] ";
+    }
+
+    protected void logV(String msg) {
+        if (VDBG) {
+            Slog.v(getClassName(), getLogPrefix() + msg);
+            LOCAL_LOG.log("[VERBOSE ] " + getClassName() + getLogPrefix() + msg);
+        }
+    }
+
+    protected void logInfo(String msg) {
+        Slog.i(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logW(String msg) {
+        Slog.w(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WARN ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logWtf(String msg) {
+        Slog.wtf(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected static void logV(String className, String msgWithPrefix) {
+        if (VDBG) {
+            Slog.wtf(className, msgWithPrefix);
+            LOCAL_LOG.log("[VERBOSE ] " + className + msgWithPrefix);
+        }
+    }
+
+    protected static void logWtf(String className, String msgWithPrefix) {
+        Slog.wtf(className, msgWithPrefix);
+        LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
+    }
+}
diff --git a/services/core/java/com/android/server/wearable/OWNERS b/services/core/java/com/android/server/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/services/core/java/com/android/server/wearable/OWNERS
+++ b/services/core/java/com/android/server/wearable/OWNERS
@@ -1,3 +1 @@
-charliewang@google.com
-oni@google.com
-volnov@google.com
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index cd48f5d..106be5f 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -218,7 +218,7 @@
                 PersistableBundle data,
                 SharedMemory sharedMemory,
                 RemoteCallback callback) {
-            Slog.i(TAG, "WearableSensingManagerInternal provideData.");
+            Slog.d(TAG, "WearableSensingManagerInternal provideData.");
             Objects.requireNonNull(data);
             Objects.requireNonNull(callback);
             mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 9e56d7a..8aaf76a 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1596,7 +1596,9 @@
             // skip commitVisibility call in setVisibility cause the activity won't visible here.
             // Call it again to make sure the activity could be visible while handling the pending
             // animation.
-            activity.commitVisibility(true, true);
+            // Do not performLayout during prepare animation, because it could cause focus window
+            // change. Let that happen after the BackNavigationInfo has returned to shell.
+            activity.commitVisibility(true, false /* performLayout */);
             activity.mTransitionController.mSnapshotController
                     .mActivitySnapshotController.addOnBackPressedActivity(activity);
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6033220..02b3f15 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -808,7 +808,6 @@
         mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
         mWmService.mSyncEngine.onSurfacePlacement();
-        mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
 
         checkAppTransitionReady(surfacePlacer);
 
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 070bd4b..2ccd1e4 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 7.2.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -145,4 +145,4 @@
             line="7158"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml
index 8489c17..a311d07 100644
--- a/services/lint-baseline.xml
+++ b/services/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="SimpleManualPermissionEnforcement"
@@ -56,4 +56,4 @@
             column="13"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/print/lint-baseline.xml b/services/print/lint-baseline.xml
index 1bf031a..11c0cc8e 100644
--- a/services/print/lint-baseline.xml
+++ b/services/print/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="SimpleManualPermissionEnforcement"
@@ -12,4 +12,4 @@
             column="13"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
index e3954355..4fcdbfc 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
@@ -33,7 +33,6 @@
         ":BackgroundInstallControlServiceTestApp",
         ":BackgroundInstallControlMockApp1",
         ":BackgroundInstallControlMockApp2",
-        ":BackgroundInstallControlMockApp3",
     ],
     test_suites: [
         "general-tests",
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
index 031d57f..1e7a78a 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
+++ b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
@@ -29,14 +29,11 @@
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
         <option name="push-file"
-            key="BackgroundInstallControlMockApp1.apk"
-            value="/data/local/tmp/BackgroundInstallControlMockApp1.apk" />
+                key="BackgroundInstallControlMockApp1.apk"
+                value="/data/local/tmp/BackgroundInstallControlMockApp1.apk" />
         <option name="push-file"
-            key="BackgroundInstallControlMockApp2.apk"
-            value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" />
-        <option name="push-file"
-            key="BackgroundInstallControlMockApp3.apk"
-            value="/data/local/tmp/BackgroundInstallControlMockApp3.apk" />
+                key="BackgroundInstallControlMockApp2.apk"
+                value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" />
     </target_preparer>
 
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
index 5092a46..7450607 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
@@ -41,26 +41,17 @@
     private static final String MOCK_APK_FILE_1 = "BackgroundInstallControlMockApp1.apk";
     private static final String MOCK_APK_FILE_2 = "BackgroundInstallControlMockApp2.apk";
 
-    // TODO: Move the silent installs to test-app using {@link
-    // BackgroundInstallControlServiceTest#installPackage(String, String)} and remove deviceConfig
-    // branch in BICS.
-    // b/310983905
     @Test
     public void testGetMockBackgroundInstalledPackages() throws Exception {
-        installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1);
+        installPackage(TEST_DATA_DIR  + MOCK_APK_FILE_1);
         installPackage(TEST_DATA_DIR + MOCK_APK_FILE_2);
 
         assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNotNull();
         assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNotNull();
 
-        assertThat(
-                getDevice()
-                        .setProperty(
-                                "debug.transparency.bg-install-apps",
-                                MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2))
-                .isTrue();
-        runDeviceTest(
-                "BackgroundInstallControlServiceTest", "testGetMockBackgroundInstalledPackages");
+        assertThat(getDevice().setProperty("debug.transparency.bg-install-apps",
+                    MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)).isTrue();
+        runDeviceTest("testGetMockBackgroundInstalledPackages");
         assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_1)).isNull();
         assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_2)).isNull();
 
@@ -68,30 +59,16 @@
         assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNull();
     }
 
-    @Test
-    public void testRegisterCallback() throws Exception {
-        runDeviceTest(
-                "BackgroundInstallControlServiceTest",
-                "testRegisterBackgroundInstallControlCallback");
-    }
-
-    @Test
-    public void testUnregisterCallback() throws Exception {
-        runDeviceTest(
-                "BackgroundInstallControlServiceTest",
-                "testUnregisterBackgroundInstallControlCallback");
-    }
-
     private void installPackage(String path) throws DeviceNotAvailableException {
         String cmd = "pm install -t --force-queryable " + path;
         CommandResult result = getDevice().executeShellV2Command(cmd);
         assertThat(result.getStatus() == CommandStatus.SUCCESS).isTrue();
     }
 
-    private void runDeviceTest(String testName, String method) throws DeviceNotAvailableException {
+    private void runDeviceTest(String method) throws DeviceNotAvailableException {
         var options = new DeviceTestRunOptions(PACKAGE_NAME);
-        options.setTestClassName(PACKAGE_NAME + "." + testName);
+        options.setTestClassName(PACKAGE_NAME + ".BackgroundInstallControlServiceTest");
         options.setTestMethodName(method);
         runDeviceTests(options);
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
index b5b8ea0..1fa1f84 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
@@ -21,9 +21,6 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:label="APCT tests for background install control service"
         android:targetPackage="com.android.server.pm.test.app" />
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
index f033fed..b74e561 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
@@ -16,256 +16,54 @@
 
 package com.android.server.pm.test.app;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.IBackgroundInstallControlService;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
-import android.os.Bundle;
-import android.os.IRemoteCallback;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.Pair;
+import android.os.UserHandle;
 
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.FileInputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 @RunWith(AndroidJUnit4.class)
 public class BackgroundInstallControlServiceTest {
     private static final String TAG = "BackgroundInstallControlServiceTest";
-    private static final String ACTION_INSTALL_COMMIT =
-            "com.android.server.pm.test.app.BackgroundInstallControlServiceTest"
-                    + ".ACTION_INSTALL_COMMIT";
-    private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3";
 
-    private static final String TEST_DATA_DIR = "/data/local/tmp/";
-
-    private static final String MOCK_APK_FILE = "BackgroundInstallControlMockApp3.apk";
     private IBackgroundInstallControlService mIBics;
 
     @Before
     public void setUp() {
-        mIBics =
-                IBackgroundInstallControlService.Stub.asInterface(
-                        ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+        mIBics = IBackgroundInstallControlService.Stub.asInterface(
+                ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
         assertThat(mIBics).isNotNull();
     }
 
-    @After
-    public void tearDown() {
-        runShellCommand("pm uninstall " + MOCK_PACKAGE_NAME);
-    }
-
     @Test
     public void testGetMockBackgroundInstalledPackages() throws RemoteException {
-        ParceledListSlice<PackageInfo> slice =
-                ShellIdentityUtils.invokeMethodWithShellPermissions(
-                        mIBics,
-                        (bics) -> {
-                            try {
-                                return bics.getBackgroundInstalledPackages(
-                                        PackageManager.MATCH_ALL, Process.myUserHandle()
-                                                .getIdentifier());
-                            } catch (RemoteException e) {
-                                throw new RuntimeException(e);
-                            }
-                        },
-                        QUERY_ALL_PACKAGES);
+        ParceledListSlice<PackageInfo> slice = mIBics.getBackgroundInstalledPackages(
+                    PackageManager.MATCH_ALL,
+                    UserHandle.USER_ALL);
         assertThat(slice).isNotNull();
 
         var packageList = slice.getList();
         assertThat(packageList).isNotNull();
         assertThat(packageList).hasSize(2);
 
-        var expectedPackageNames =
-                Set.of(
-                        "com.android.servicestests.apps.bicmockapp1",
-                        "com.android.servicestests.apps.bicmockapp2");
-        var actualPackageNames =
-                packageList.stream()
-                        .map((packageInfo) -> packageInfo.packageName)
-                        .collect(Collectors.toSet());
+        var expectedPackageNames = Set.of("com.android.servicestests.apps.bicmockapp1",
+                "com.android.servicestests.apps.bicmockapp2");
+        var actualPackageNames = packageList.stream().map((packageInfo) -> packageInfo.packageName)
+                .collect(Collectors.toSet());
         assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames);
     }
-
-    @Test
-    public void testRegisterBackgroundInstallControlCallback()
-            throws Exception {
-        String testPackageName = "test";
-        int testUserId = 1;
-        ArrayList<Pair<String, Integer>> sharedResource = new ArrayList<>();
-        IRemoteCallback testCallback =
-                new IRemoteCallback.Stub() {
-                    private final ArrayList<Pair<String, Integer>> mArray = sharedResource;
-
-                    @Override
-                    public void sendResult(Bundle data) throws RemoteException {
-                        mArray.add(new Pair(testPackageName, testUserId));
-                    }
-                };
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
-                mIBics,
-                (bics) -> {
-                    try {
-                        bics.registerBackgroundInstallCallback(testCallback);
-                    } catch (RemoteException e) {
-                        throw new RuntimeException(e);
-                    }
-                },
-                QUERY_ALL_PACKAGES,
-                INTERACT_ACROSS_USERS_FULL);
-        installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME);
-
-        assertUntil(() -> sharedResource.size() == 1, 2000);
-        assertThat(sharedResource.get(0).first).isEqualTo(testPackageName);
-        assertThat(sharedResource.get(0).second).isEqualTo(testUserId);
-    }
-
-    @Test
-    public void testUnregisterBackgroundInstallControlCallback() {
-        String testValue = "test";
-        ArrayList<String> sharedResource = new ArrayList<>();
-        IRemoteCallback testCallback =
-                new IRemoteCallback.Stub() {
-                    private final ArrayList<String> mArray = sharedResource;
-
-                    @Override
-                    public void sendResult(Bundle data) throws RemoteException {
-                        mArray.add(testValue);
-                    }
-                };
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
-                mIBics,
-                (bics) -> {
-                    try {
-                        bics.registerBackgroundInstallCallback(testCallback);
-                        bics.unregisterBackgroundInstallCallback(testCallback);
-                    } catch (RemoteException e) {
-                        throw new RuntimeException(e);
-                    }
-                },
-                QUERY_ALL_PACKAGES,
-                INTERACT_ACROSS_USERS_FULL);
-        installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME);
-
-        assertUntil(() -> sharedResource.isEmpty(), 2000);
-    }
-
-    private static boolean installPackage(String apkPath, String packageName) {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final CountDownLatch installLatch = new CountDownLatch(1);
-        final BroadcastReceiver installReceiver =
-                new BroadcastReceiver() {
-                    public void onReceive(Context context, Intent intent) {
-                        int packageInstallStatus =
-                                intent.getIntExtra(
-                                        PackageInstaller.EXTRA_STATUS,
-                                        PackageInstaller.STATUS_FAILURE_INVALID);
-                        if (packageInstallStatus == PackageInstaller.STATUS_SUCCESS) {
-                            installLatch.countDown();
-                        }
-                    }
-                };
-        final IntentFilter intentFilter = new IntentFilter(ACTION_INSTALL_COMMIT);
-        context.registerReceiver(installReceiver, intentFilter, Context.RECEIVER_EXPORTED);
-
-        PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
-        PackageInstaller.SessionParams params =
-                new PackageInstaller.SessionParams(
-                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED);
-        try {
-            int sessionId = packageInstaller.createSession(params);
-            PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-            OutputStream out = session.openWrite(packageName, 0, -1);
-            FileInputStream fis = new FileInputStream(apkPath);
-            byte[] buffer = new byte[65536];
-            int size;
-            while ((size = fis.read(buffer)) != -1) {
-                out.write(buffer, 0, size);
-            }
-            session.fsync(out);
-            fis.close();
-            out.close();
-
-            runWithShellPermissionIdentity(
-                    () -> {
-                        session.commit(createPendingIntent(context).getIntentSender());
-                        installLatch.await(5, TimeUnit.SECONDS);
-                    });
-            return true;
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static PendingIntent createPendingIntent(Context context) {
-        PendingIntent pendingIntent =
-                PendingIntent.getBroadcast(
-                        context,
-                        1,
-                        new Intent(ACTION_INSTALL_COMMIT)
-                                .setPackage(
-                                        BackgroundInstallControlServiceTest.class.getPackageName()),
-                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
-        return pendingIntent;
-    }
-
-    private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command)
-            throws Exception {
-        InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity();
-        try {
-            command.run();
-        } finally {
-            InstrumentationRegistry.getInstrumentation()
-                    .getUiAutomation()
-                    .dropShellPermissionIdentity();
-        }
-    }
-
-    private static void assertUntil(Supplier<Boolean> condition, int timeoutMs) {
-        long endTime = System.currentTimeMillis() + timeoutMs;
-        while (System.currentTimeMillis() <= endTime) {
-            if (condition.get()) return;
-            try {
-                Thread.sleep(10);
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        assertThat(condition.get()).isTrue();
-    }
 }
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
index 39b0ff7..7804f4c 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
@@ -50,11 +50,3 @@
         "--rename-manifest-package com.android.servicestests.apps.bicmockapp2",
     ],
 }
-
-android_test_helper_app {
-    name: "BackgroundInstallControlMockApp3",
-    defaults: ["bic-mock-app-defaults"],
-    aaptflags: [
-        "--rename-manifest-package com.android.servicestests.apps.bicmockapp3",
-    ],
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
deleted file mode 100644
index e1fce9b..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
+++ /dev/null
@@ -1,95 +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.server.pm;
-
-import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY;
-import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_USER_ID_KEY;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.os.Bundle;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-
-/** Unit tests for {@link BackgroundInstallControlCallbackHelperTest} */
-@Presubmit
-@RunWith(JUnit4.class)
-public class BackgroundInstallControlCallbackHelperTest {
-
-    private final IRemoteCallback mCallback =
-            spy(
-                    new IRemoteCallback.Stub() {
-                        @Override
-                        public void sendResult(Bundle extras) {}
-                    });
-
-    private BackgroundInstallControlCallbackHelper mCallbackHelper;
-
-    @Before
-    public void setup() {
-        mCallbackHelper = new BackgroundInstallControlCallbackHelper();
-    }
-
-    @Test
-    public void registerBackgroundInstallControlCallback_registers_successfully() {
-        mCallbackHelper.registerBackgroundInstallCallback(mCallback);
-
-        synchronized (mCallbackHelper.mCallbacks) {
-            assertEquals(1, mCallbackHelper.mCallbacks.getRegisteredCallbackCount());
-            assertEquals(mCallback, mCallbackHelper.mCallbacks.getRegisteredCallbackItem(0));
-        }
-    }
-
-    @Test
-    public void unregisterBackgroundInstallControlCallback_unregisters_successfully() {
-        synchronized (mCallbackHelper.mCallbacks) {
-            mCallbackHelper.mCallbacks.register(mCallback);
-        }
-
-        mCallbackHelper.unregisterBackgroundInstallCallback(mCallback);
-
-        synchronized (mCallbackHelper.mCallbacks) {
-            assertEquals(0, mCallbackHelper.mCallbacks.getRegisteredCallbackCount());
-        }
-    }
-
-    @Test
-    public void notifyAllCallbacks_broadcastsToCallbacks()
-            throws RemoteException {
-        String testPackageName = "testname";
-        int testUserId = 1;
-        mCallbackHelper.registerBackgroundInstallCallback(mCallback);
-
-        mCallbackHelper.notifyAllCallbacks(testUserId, testPackageName);
-
-        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        verify(mCallback, after(1000).times(1)).sendResult(bundleCaptor.capture());
-        Bundle receivedBundle = bundleCaptor.getValue();
-        assertEquals(testPackageName, receivedBundle.getString(FLAGGED_PACKAGE_NAME_KEY));
-        assertEquals(testUserId, receivedBundle.getInt(FLAGGED_USER_ID_KEY));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 3069d25..daf18ed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -16,10 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -31,7 +27,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -102,6 +97,7 @@
     private Looper mLooper;
     private File mFile;
 
+
     @Mock
     private Context mContext;
     @Mock
@@ -112,12 +108,8 @@
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     @Mock
     private PermissionManagerServiceInternal mPermissionManager;
-    @Mock
-    private BackgroundInstallControlCallbackHelper mCallbackHelper;
-
     @Captor
     private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
-
     @Captor
     private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
 
@@ -127,12 +119,11 @@
 
         mTestLooper = new TestLooper();
         mLooper = mTestLooper.getLooper();
-        mFile =
-                new File(
-                        InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
-                        "test");
-        mBackgroundInstallControlService =
-                new BackgroundInstallControlService(new MockInjector(mContext));
+        mFile = new File(
+                InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
+                "test");
+        mBackgroundInstallControlService = new BackgroundInstallControlService(
+                new MockInjector(mContext));
 
         verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture());
         mUsageEventListener = mUsageEventListenerCaptor.getValue();
@@ -152,7 +143,8 @@
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         mBackgroundInstallControlService.initBackgroundInstalledPackages();
         assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        assertEquals(0, mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
+        assertEquals(0,
+                mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
     }
 
     @Test
@@ -169,9 +161,12 @@
         // Write test data to the file on the disk.
         try {
             ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
-            long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
-            protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
-            protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+            long token = protoOutputStream.start(
+                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
             protoOutputStream.end(token);
             protoOutputStream.flush();
             atomicFile.finishWrite(fileOutputStream);
@@ -203,14 +198,20 @@
         try {
             ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
 
-            long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
-            protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
-            protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+            long token = protoOutputStream.start(
+                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
             protoOutputStream.end(token);
 
-            token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
-            protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
-            protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
+            token = protoOutputStream.start(
+                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
+            protoOutputStream.write(
+                    BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
             protoOutputStream.end(token);
 
             protoOutputStream.flush();
@@ -240,7 +241,7 @@
         // Read the file on the disk to verify
         var packagesInDisk = new SparseSetArray<>();
         AtomicFile atomicFile = new AtomicFile(mFile);
-        try (FileInputStream fileInputStream = atomicFile.openRead()) {
+        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
             ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
 
             while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -248,25 +249,23 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token =
-                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName =
-                                    protoInputStream.readString(
-                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId =
-                                    protoInputStream.readInt(
-                                            BackgroundInstalledPackageProto.USER_ID)
-                                            - 1;
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
                             break;
                         default:
-                            fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
+                            fail("Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -297,7 +296,7 @@
         // Read the file on the disk to verify
         var packagesInDisk = new SparseSetArray<>();
         AtomicFile atomicFile = new AtomicFile(mFile);
-        try (FileInputStream fileInputStream = atomicFile.openRead()) {
+        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
             ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
 
             while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -305,25 +304,23 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token =
-                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName =
-                                    protoInputStream.readString(
-                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId =
-                                    protoInputStream.readInt(
-                                            BackgroundInstalledPackageProto.USER_ID)
-                                            - 1;
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
                             break;
                         default:
-                            fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
+                            fail("Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -356,7 +353,7 @@
         // Read the file on the disk to verify
         var packagesInDisk = new SparseSetArray<>();
         AtomicFile atomicFile = new AtomicFile(mFile);
-        try (FileInputStream fileInputStream = atomicFile.openRead()) {
+        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
             ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
 
             while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -364,25 +361,23 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token =
-                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token = protoInputStream.start(
+                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName =
-                                    protoInputStream.readString(
-                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName = protoInputStream.readString(
+                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId =
-                                    protoInputStream.readInt(
-                                            BackgroundInstalledPackageProto.USER_ID)
-                                            - 1;
+                            userId = protoInputStream.readInt(
+                                    BackgroundInstalledPackageProto.USER_ID) - 1;
                             break;
                         default:
-                            fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
+                            fail("Undefined field in proto: "
+                                    + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -404,55 +399,51 @@
 
     @Test
     public void testHandleUsageEvent_permissionDenied() {
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0);
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
     }
 
     @Test
     public void testHandleUsageEvent_permissionGranted() {
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0);
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
-        assertEquals(
-                1, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        assertEquals(1,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
     }
 
     @Test
     public void testHandleUsageEvent_ignoredEvent() {
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.USER_INTERACTION, USER_ID_1, INSTALLER_NAME_1, 0);
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
+                USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
     }
 
     @Test
     public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() {
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_1);
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -470,18 +461,14 @@
 
     @Test
     public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() {
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_1);
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -499,23 +486,16 @@
 
     @Test
     public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() {
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_1);
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_3);
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -537,13 +517,12 @@
 
     @Test
     public void testHandleUsageEvent_firstNoneActivityResumed() {
-        assertEquals(
-                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        assertEquals(0,
+                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -556,26 +535,27 @@
     }
 
     @Test
-    public void testHandleUsageEvent_packageAddedNoUsageEvent()
-            throws NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedNoUsageEvent() throws
+            NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo =
-                new InstallSourceInfo(
-                        /* initiatingPackageName= */ INSTALLER_NAME_1,
-                        /* initiatingPackageSigningInfo= */ null,
-                        /* originatingPackageName= */ null,
-                        /* installingPackageName= */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
-                .thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(PACKAGE_NAME_1),
+                any(),
+                anyInt())
+        ).thenReturn(appInfo);
 
-        long createTimestamp =
-                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(
-                appInfo,
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -592,26 +572,27 @@
     }
 
     @Test
-    public void testHandleUsageEvent_packageAddedInsideTimeFrame()
-            throws NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws
+            NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo =
-                new InstallSourceInfo(
-                        /* initiatingPackageName= */ INSTALLER_NAME_1,
-                        /* initiatingPackageSigningInfo= */ null,
-                        /* originatingPackageName= */ null,
-                        /* installingPackageName= */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
-                .thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(PACKAGE_NAME_1),
+                any(),
+                anyInt())
+        ).thenReturn(appInfo);
 
-        long createTimestamp =
-                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(
-                appInfo,
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -623,16 +604,12 @@
         // The 2 usage events make the package adding inside a time frame.
         // So it's not a background install. Thus, it's null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_1);
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -640,26 +617,27 @@
     }
 
     @Test
-    public void testHandleUsageEvent_packageAddedOutsideTimeFrame1()
-            throws NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws
+            NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo =
-                new InstallSourceInfo(
-                        /* initiatingPackageName= */ INSTALLER_NAME_1,
-                        /* initiatingPackageSigningInfo= */ null,
-                        /* originatingPackageName= */ null,
-                        /* installingPackageName= */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
-                .thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(PACKAGE_NAME_1),
+                any(),
+                anyInt())
+        ).thenReturn(appInfo);
 
-        long createTimestamp =
-                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(
-                appInfo,
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -672,16 +650,12 @@
         // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
         // it's a background install. Thus, it's not null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -691,28 +665,28 @@
         assertEquals(1, packages.size());
         assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
     }
-
     @Test
-    public void testHandleUsageEvent_packageAddedOutsideTimeFrame2()
-            throws NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws
+            NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo =
-                new InstallSourceInfo(
-                        /* initiatingPackageName= */ INSTALLER_NAME_1,
-                        /* initiatingPackageSigningInfo= */ null,
-                        /* originatingPackageName= */ null,
-                        /* installingPackageName= */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
-                .thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(PACKAGE_NAME_1),
+                any(),
+                anyInt())
+        ).thenReturn(appInfo);
 
-        long createTimestamp =
-                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(
-                appInfo,
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -726,16 +700,12 @@
         // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
         // it's a background install. Thus, it's not null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_2,
-                INSTALLER_NAME_2,
-                USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -745,31 +715,31 @@
         assertEquals(1, packages.size());
         assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
     }
-
     @Test
-    public void testHandleUsageEvent_packageAddedThroughAdb()
-            throws NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedThroughAdb() throws
+            NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the
         // initiatingPackageName used to be null but is now "com.android.shell". This test ensures
         // that the behavior is still the same for when the initiatingPackageName is null.
-        InstallSourceInfo installSourceInfo =
-                new InstallSourceInfo(
-                        /* initiatingPackageName= */ null,
-                        /* initiatingPackageSigningInfo= */ null,
-                        /* originatingPackageName= */ null,
-                        /* installingPackageName= */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ null,
+                /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
         // b/265203007
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
-                .thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(PACKAGE_NAME_1),
+                any(),
+                anyInt())
+        ).thenReturn(appInfo);
 
-        long createTimestamp =
-                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(
-                appInfo,
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -781,16 +751,12 @@
         // for ADB installs the initiatingPackageName used to be null, despite being detected
         // as a background install. Since we do not want to treat side-loaded apps as background
         // install getBackgroundInstalledPackages() is expected to return null
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -798,31 +764,31 @@
         var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
         assertNull(packages);
     }
-
     @Test
-    public void testHandleUsageEvent_packageAddedThroughAdb2()
-            throws NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedThroughAdb2() throws
+            NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the
         // initiatingPackageName used to be null but is now "com.android.shell". This test ensures
         // that the behavior is still the same after this change.
-        InstallSourceInfo installSourceInfo =
-                new InstallSourceInfo(
-                        /* initiatingPackageName= */ "com.android.shell",
-                        /* initiatingPackageSigningInfo= */ null,
-                        /* originatingPackageName= */ null,
-                        /* installingPackageName= */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ "com.android.shell",
+                /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
         // b/265203007
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
-                .thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(PACKAGE_NAME_1),
+                any(),
+                anyInt())
+        ).thenReturn(appInfo);
 
-        long createTimestamp =
-                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(
-                appInfo,
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -834,16 +800,12 @@
         // for ADB installs the initiatingPackageName is com.android.shell, despite being detected
         // as a background install. Since we do not want to treat side-loaded apps as background
         // install getBackgroundInstalledPackages() is expected to return null
-        doReturn(PERMISSION_GRANTED)
-                .when(mPermissionManager)
-                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(
-                UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1,
-                INSTALLER_NAME_1,
-                USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(
-                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -851,7 +813,6 @@
         var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
         assertNull(packages);
     }
-
     @Test
     public void testPackageRemoved() {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
@@ -898,7 +859,8 @@
         packages.add(packageInfo2);
         var packageInfo3 = makePackageInfo(PACKAGE_NAME_3);
         packages.add(packageInfo3);
-        doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(any(), anyInt());
+        doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(
+                any(), anyInt());
 
         var resultPackages =
                 mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1);
@@ -908,44 +870,18 @@
         assertFalse(resultPackages.getList().contains(packageInfo3));
     }
 
-    @Test(expected = SecurityException.class)
-    public void enforceCallerQueryPackagesPermissionsThrowsSecurityException() {
-        doThrow(new SecurityException("test")).when(mContext)
-                .enforceCallingPermission(eq(QUERY_ALL_PACKAGES), anyString());
-
-        mBackgroundInstallControlService.enforceCallerQueryPackagesPermissions();
-    }
-
-    @Test
-    public void enforceCallerQueryPackagesPermissionsDoesNotThrowSecurityException() {
-        //enforceCallerQueryPackagesPermissions do not throw
-
-        mBackgroundInstallControlService.enforceCallerQueryPackagesPermissions();
-    }
-
-    @Test(expected = SecurityException.class)
-    public void enforceCallerInteractCrossUserPermissionsThrowsSecurityException() {
-        doThrow(new SecurityException("test")).when(mContext)
-                .enforceCallingPermission(eq(INTERACT_ACROSS_USERS_FULL), anyString());
-
-        mBackgroundInstallControlService.enforceCallerInteractCrossUserPermissions();
-    }
-    @Test
-    public void enforceCallerInteractCrossUserPermissionsDoesNotThrowSecurityException() {
-        //enforceCallerQueryPackagesPermissions do not throw
-
-        mBackgroundInstallControlService.enforceCallerInteractCrossUserPermissions();
-    }
-
     /**
      * Mock a usage event occurring.
      *
      * @param usageEventId id of a usage event
-     * @param userId       user id of a usage event
-     * @param pkgName      package name of a usage event
-     * @param timestamp    timestamp of a usage event
+     * @param userId user id of a usage event
+     * @param pkgName package name of a usage event
+     * @param timestamp timestamp of a usage event
      */
-    private void generateUsageEvent(int usageEventId, int userId, String pkgName, long timestamp) {
+    private void generateUsageEvent(int usageEventId,
+            int userId,
+            String pkgName,
+            long timestamp) {
         Event event = new Event(usageEventId, timestamp);
         event.mPackage = pkgName;
         mUsageEventListener.onUsageEvent(userId, event);
@@ -999,10 +935,5 @@
         public File getDiskFile() {
             return mFile;
         }
-
-        @Override
-        public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
-            return mCallbackHelper;
-        }
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 53ca704..bf850cf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -44,8 +44,10 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
+import android.os.BadParcelableException;
 import android.os.Binder;
 import android.os.Build;
+import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -99,6 +101,20 @@
     }
 
     @Test
+    public void testGetActiveNotifications_handlesBinderErrors() throws RemoteException {
+        TestListenerService service = new TestListenerService();
+        INotificationManager noMan = service.getNoMan();
+        when(noMan.getActiveNotificationsFromListener(any(), any(), anyInt()))
+                .thenThrow(new BadParcelableException("oops", new DeadObjectException("")));
+
+        assertNotNull(service.getActiveNotifications());
+        assertNotNull(service.getActiveNotifications(NotificationListenerService.TRIM_FULL));
+        assertNotNull(service.getActiveNotifications(new String[0]));
+        assertNull(service.getActiveNotifications(
+                new String[0], NotificationListenerService.TRIM_LIGHT));
+    }
+
+    @Test
     public void testGetActiveNotifications_preP_mapsExtraPeople() throws RemoteException {
         TestListenerService service = new TestListenerService();
         service.attachBaseContext(mContext);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 177d645..dd252f3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -60,6 +60,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.time.Instant;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -407,6 +408,7 @@
         rule.userModifiedFields = 16;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
+        rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
 
         Parcel parcel = Parcel.obtain();
         rule.writeToParcel(parcel, 0);
@@ -432,9 +434,10 @@
         assertEquals(rule.userModifiedFields, parceled.userModifiedFields);
         assertEquals(rule.triggerDescription, parceled.triggerDescription);
         assertEquals(rule.zenPolicy, parceled.zenPolicy);
+        assertEquals(rule.deletionInstant, parceled.deletionInstant);
+
         assertEquals(rule, parceled);
         assertEquals(rule.hashCode(), parceled.hashCode());
-
     }
 
     @Test
@@ -510,6 +513,7 @@
         rule.userModifiedFields = 4;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
+        rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         writeRuleXml(rule, baos);
@@ -539,6 +543,7 @@
         assertEquals(rule.userModifiedFields, fromXml.userModifiedFields);
         assertEquals(rule.triggerDescription, fromXml.triggerDescription);
         assertEquals(rule.iconResName, fromXml.iconResName);
+        assertEquals(rule.deletionInstant, fromXml.deletionInstant);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 7e92e42..9d7cf53 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -65,18 +65,22 @@
 @TestableLooper.RunWithLooper
 public class ZenModeDiffTest extends UiServiceTestCase {
     // Base set of exempt fields independent of fields that are enabled/disabled via flags.
-    // version is not included in the diff; manual & automatic rules have special handling
+    // version is not included in the diff; manual & automatic rules have special handling;
+    // deleted rules are not included in the diff.
     public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
-            Set.of("version", "manualRule", "automaticRules");
+            android.app.Flags.modesApi()
+                    ? Set.of("version", "manualRule", "automaticRules", "deletedRules")
+                    : Set.of("version", "manualRule", "automaticRules");
 
     // Differences for flagged fields are only generated if the flag is enabled.
-    // TODO: b/310620812 - Remove this exempt list when flag is inlined.
+    // "Metadata" fields (userModifiedFields, deletionInstant) are not compared.
     private static final Set<String> ZEN_RULE_EXEMPT_FIELDS =
             android.app.Flags.modesApi()
-                    ? Set.of()
+                    ? Set.of("userModifiedFields", "deletionInstant")
                     : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
                             RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
-                            RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, RuleDiff.FIELD_USER_MODIFIED_FIELDS);
+                            RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields",
+                            "deletionInstant");
 
     // allowPriorityChannels is flagged by android.app.modes_api
     public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS =
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 0224ff3..9e3e336 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -43,6 +43,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
@@ -92,6 +93,7 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.AppGlobals;
@@ -104,6 +106,7 @@
 import android.content.ContentResolver;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
@@ -116,6 +119,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Process;
+import android.os.SimpleClock;
 import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -172,12 +176,16 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @SmallTest
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@@ -232,6 +240,7 @@
     @Mock PackageManager mPackageManager;
     private Resources mResources;
     private TestableLooper mTestableLooper;
+    private final TestClock mTestClock = new TestClock();
     private ZenModeHelper mZenModeHelper;
     private ContentResolver mContentResolver;
     @Mock DeviceEffectsApplier mDeviceEffectsApplier;
@@ -269,7 +278,7 @@
         mConditionProviders.addSystemProvider(new CountdownConditionProvider());
         mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
         mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager);
-        mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(),
+        mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(), mTestClock,
                 mConditionProviders, mTestFlagResolver, mZenModeEventLogger);
 
         ResolveInfo ri = new ResolveInfo();
@@ -1198,7 +1207,7 @@
     @Test
     public void ruleUidAutomaticZenRuleRemovedUpdatesCache() throws Exception {
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         setupZenConfig();
         // one enabled automatic rule
@@ -1780,7 +1789,7 @@
     public void testDoNotUpdateModifiedDefaultAutoRule() {
         // mDefaultConfig is set to default config in setup by getDefaultConfigParser
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         // shouldn't update rule that's been modified
         ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
@@ -1806,7 +1815,7 @@
     public void testDoNotUpdateEnabledDefaultAutoRule() {
         // mDefaultConfig is set to default config in setup by getDefaultConfigParser
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         // shouldn't update the rule that's enabled
         ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
@@ -1833,7 +1842,7 @@
         // mDefaultConfig is set to default config in setup by getDefaultConfigParser
         final String defaultRuleName = "rule name test";
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         // will update rule that is not enabled and modified
         ZenModeConfig.ZenRule customDefaultRule = new ZenModeConfig.ZenRule();
@@ -4318,6 +4327,324 @@
     }
 
     @Test
+    public void removeAndAddAutomaticZenRule_wasCustomized_isRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).canUpdate()).isTrue();
+
+        // User customizes it.
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+
+        // App deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+        // App adds it again.
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // Verify that the rule was restored:
+        // - id and creation time is the same as the original one.
+        // - ZenPolicy is the one that the user had set.
+        // - rule still has the user-modified fields.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(1000); // And not 3000.
+        assertThat(newRuleId).isEqualTo(ruleId);
+        assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+        assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
+                ZenPolicy.STATE_ALLOW);
+        assertThat(finalRule.getUserModifiedFields()).isEqualTo(
+                AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+        assertThat(finalRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+                ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS);
+
+        // Also, we discarded the "deleted rule" since we already used it for restoration.
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+    }
+
+    @Test
+    public void removeAndAddAutomaticZenRule_wasNotCustomized_isNotRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a single rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+        // App deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+
+        // App adds it again.
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // Verify that the rule was recreated. This means id and creation time are new.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(3000);
+        assertThat(newRuleId).isNotEqualTo(ruleId);
+    }
+
+    @Test
+    public void removeAndAddAutomaticZenRule_recreatedButNotByApp_isNotRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a single rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+        // User customizes it.
+        mTestClock.advanceByMillis(1000);
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+
+        // App deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+        // User creates it again (unusual case, but ok).
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_USER, "add it anew", CUSTOM_PKG_UID);
+
+        // Verify that the rule was recreated. This means id and creation time are new, and the rule
+        // matches the latest data supplied to addAZR.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(4000);
+        assertThat(newRuleId).isNotEqualTo(ruleId);
+        assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
+        assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
+                ZenPolicy.STATE_DISALLOW);
+
+        // Also, we discarded the "deleted rule" since we're not interested in recreating it.
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+    }
+
+    @Test
+    public void removeAndAddAutomaticZenRule_removedByUser_isNotRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a single rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+        // User customizes it.
+        mTestClock.advanceByMillis(1000);
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+
+        // User deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+
+        // App creates it again.
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // Verify that the rule was recreated. This means id and creation time are new.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(4000);
+        assertThat(newRuleId).isNotEqualTo(ruleId);
+    }
+
+    @Test
+    public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
+                PERMISSION_GRANTED); // So that canManageAZR passes although packages don't match.
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        // Start with a bunch of customized rules where conditionUris are not unique.
+        String id1 = mZenModeHelper.addAutomaticZenRule("pkg1",
+                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id2 = mZenModeHelper.addAutomaticZenRule("pkg1",
+                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id3 = mZenModeHelper.addAutomaticZenRule("pkg1",
+                new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id4 = mZenModeHelper.addAutomaticZenRule("pkg2",
+                new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id5 = mZenModeHelper.addAutomaticZenRule("pkg2",
+                new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
+            zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+        }
+
+        mZenModeHelper.removeAutomaticZenRule(id1, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id2, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id3, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id4, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id5, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
+                .containsExactly("pkg1|uri1", "pkg1|uri2", "pkg2|uri1");
+        assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(zr -> zr.name)
+                .collect(Collectors.toList()))
+                .containsExactly("Test1", "Test3", "Test5");
+    }
+
+    @Test
+    public void removeAllZenRules_preservedForRestoring() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+
+        for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
+            zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+        }
+
+        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP,
+                "begone", CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(2);
+    }
+
+    @Test
+    public void removeAllZenRules_fromSystem_deletesPreservedRulesToo() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        // Start with deleted rules from 2 different packages.
+        Instant now = Instant.ofEpochMilli(1701796461000L);
+        ZenRule pkg1Rule = newZenRule("pkg1", now.minus(1, ChronoUnit.DAYS), now);
+        ZenRule pkg2Rule = newZenRule("pkg2", now.minus(2, ChronoUnit.DAYS), now);
+        mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg1Rule), pkg1Rule);
+        mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
+
+        mZenModeHelper.removeAutomaticZenRules("pkg1",
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "goodbye pkg1", Process.SYSTEM_UID);
+
+        // Preserved rules from pkg1 are gone; those from pkg2 are still there.
+        assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(r -> r.pkg)
+                .collect(Collectors.toSet())).containsExactly("pkg2");
+    }
+
+    @Test
+    public void testRuleCleanup() throws Exception {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        Instant now = Instant.ofEpochMilli(1701796461000L);
+        Instant yesterday = now.minus(1, ChronoUnit.DAYS);
+        Instant aWeekAgo = now.minus(7, ChronoUnit.DAYS);
+        Instant twoMonthsAgo = now.minus(60, ChronoUnit.DAYS);
+        mTestClock.setNowMillis(now.toEpochMilli());
+
+        when(mPackageManager.getPackageInfo(eq("good_pkg"), anyInt()))
+                .thenReturn(new PackageInfo());
+        when(mPackageManager.getPackageInfo(eq("bad_pkg"), anyInt()))
+                .thenThrow(new PackageManager.NameNotFoundException("bad_pkg is not here"));
+
+        // Set up a config for another user containing:
+        ZenModeConfig config = new ZenModeConfig();
+        config.user = 42;
+        mZenModeHelper.mConfigs.put(42, config);
+        // okay rules (not deleted, package exists, with a range of creation dates).
+        config.automaticRules.put("ar1", newZenRule("good_pkg", now, null));
+        config.automaticRules.put("ar2", newZenRule("good_pkg", yesterday, null));
+        config.automaticRules.put("ar3", newZenRule("good_pkg", twoMonthsAgo, null));
+        // newish rules for a missing package
+        config.automaticRules.put("ar4", newZenRule("bad_pkg", yesterday, null));
+        // oldish rules belonging to a missing package
+        config.automaticRules.put("ar5", newZenRule("bad_pkg", aWeekAgo, null));
+        // rules deleted recently
+        config.deletedRules.put("del1", newZenRule("good_pkg", twoMonthsAgo, yesterday));
+        config.deletedRules.put("del2", newZenRule("good_pkg", twoMonthsAgo, aWeekAgo));
+        // rules deleted a long time ago
+        config.deletedRules.put("del3", newZenRule("good_pkg", twoMonthsAgo, twoMonthsAgo));
+        // rules for a missing package, created recently and deleted recently
+        config.deletedRules.put("del4", newZenRule("bad_pkg", yesterday, now));
+        // rules for a missing package, created a long time ago and deleted recently
+        config.deletedRules.put("del5", newZenRule("bad_pkg", twoMonthsAgo, now));
+        // rules for a missing package, created a long time ago and deleted a long time ago
+        config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
+
+        mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+
+        assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+                .containsExactly("ar1", "ar2", "ar3", "ar4");
+        assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
+                .containsExactly("del1", "del2", "del4");
+    }
+
+    private static ZenRule newZenRule(String pkg, Instant createdAt, @Nullable Instant deletedAt) {
+        ZenRule rule = new ZenRule();
+        rule.pkg = pkg;
+        rule.creationTime = createdAt.toEpochMilli();
+        rule.deletionInstant = deletedAt;
+        // Plus stuff so that isValidAutomaticRule() passes
+        rule.name = "A rule from " + pkg + " created on " + createdAt;
+        rule.conditionId = Uri.parse(rule.name);
+        return rule;
+    }
+
+    @Test
     public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.mConfig.automaticRules.clear();
@@ -4919,4 +5246,25 @@
             return parser.nextTag();
         }
     }
+
+    private static class TestClock extends SimpleClock {
+        private long mNowMillis = 441644400000L;
+
+        private TestClock() {
+            super(ZoneOffset.UTC);
+        }
+
+        @Override
+        public long millis() {
+            return mNowMillis;
+        }
+
+        private void setNowMillis(long millis) {
+            mNowMillis = millis;
+        }
+
+        private void advanceByMillis(long millis) {
+            mNowMillis += millis;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index f5282cb..9bb2da0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3309,7 +3309,7 @@
         // keyguard to back to the app, expect IME insets is not frozen
         app.mActivityRecord.commitVisibility(true, false);
         mDisplayContent.updateImeInputAndControlTarget(app);
-        mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+        performSurfacePlacementAndWaitForWindowAnimator();
 
         assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
@@ -3358,7 +3358,7 @@
         mDisplayContent.setImeLayeringTarget(app2);
         app2.mActivityRecord.commitVisibility(true, false);
         mDisplayContent.updateImeInputAndControlTarget(app2);
-        mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+        performSurfacePlacementAndWaitForWindowAnimator();
 
         // Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets
         // to client if the app didn't request IME visible.
@@ -3412,7 +3412,7 @@
         // frozen until the input started.
         mDisplayContent.setImeLayeringTarget(app1);
         mDisplayContent.updateImeInputAndControlTarget(app1);
-        mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+        performSurfacePlacementAndWaitForWindowAnimator();
 
         assertEquals(app1, mDisplayContent.getImeInputTarget());
         assertFalse(activity1.mImeInsetsFrozenUntilStartInput);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
index 8bd5473..2d3c4bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
@@ -28,6 +28,7 @@
 import android.os.Looper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
+import android.util.Log;
 
 import androidx.test.filters.MediumTest;
 
@@ -147,6 +148,8 @@
     private void setExceptionListAndWaitForCallback(String commaSeparatedList) {
         CountDownLatch latch = new CountDownLatch(1);
         mOnUpdateDeviceConfig = rawList -> {
+            Log.i(getClass().getSimpleName(), "updateDeviceConfig expected="
+                    + commaSeparatedList + " actual=" + rawList);
             if (commaSeparatedList.equals(rawList)) {
                 latch.countDown();
             }
@@ -155,7 +158,7 @@
                 KEY_SPLASH_SCREEN_EXCEPTION_LIST, commaSeparatedList, false);
         try {
             assertTrue("Timed out waiting for DeviceConfig to be updated.",
-                    latch.await(1, TimeUnit.SECONDS));
+                    latch.await(5, TimeUnit.SECONDS));
         } catch (InterruptedException e) {
             Assert.fail(e.getMessage());
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 60e84b0..9c421ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1054,6 +1054,19 @@
     }
 
     /**
+     * Performs surface placement and waits for WindowAnimator to complete the frame. It is used
+     * to execute the callbacks if the surface placement is expected to add some callbacks via
+     * {@link WindowAnimator#addAfterPrepareSurfacesRunnable}.
+     */
+    void performSurfacePlacementAndWaitForWindowAnimator() {
+        mWm.mAnimator.ready();
+        if (!mWm.mWindowPlacerLocked.isTraversalScheduled()) {
+            mRootWindowContainer.performSurfacePlacement();
+        }
+        waitUntilWindowAnimatorIdle();
+    }
+
+    /**
      * Avoids rotating screen disturbed by some conditions. It is usually used for the default
      * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
      *
diff --git a/services/usb/lint-baseline.xml b/services/usb/lint-baseline.xml
index c2c0a35..62a2ee5 100644
--- a/services/usb/lint-baseline.xml
+++ b/services/usb/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -12,4 +12,4 @@
             column="42"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5615602..a5c6d57 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1315,7 +1315,7 @@
      * A source of phone number: the EF-MSISDN (see 3GPP TS 31.102),
      * or EF-MDN for CDMA (see 3GPP2 C.P0065-B), from UICC application.
      *
-     * <p>The availability and a of the number depends on the carrier.
+     * <p>The availability and accuracy of the number depends on the carrier.
      * The number may be updated by over-the-air update to UICC applications
      * from the carrier, or by other means with physical access to the SIM.
      */
@@ -1557,12 +1557,21 @@
      * caller can see all subscription across user profiles as it does today today even if it's
      * {@code false}.
      */
-    private boolean mIsForAllUserProfiles = false;
+    private final boolean mIsForAllUserProfiles;
 
     /** @hide */
     @UnsupportedAppUsage
     public SubscriptionManager(Context context) {
-        if (DBG) logd("SubscriptionManager created");
+        this(context, false /*isForAllUserProfiles*/);
+    }
+
+    /**  Constructor */
+    private SubscriptionManager(Context context, boolean isForAllUserProfiles) {
+        if (DBG) {
+            logd("SubscriptionManager created "
+                    + (isForAllUserProfiles ? "for all user profile" : ""));
+        }
+        mIsForAllUserProfiles = isForAllUserProfiles;
         mContext = context;
     }
 
@@ -1998,7 +2007,7 @@
     }
 
     /**
-     * Convert this subscription manager instance into one that can see all subscriptions across
+     * Create a new subscription manager instance that can see all subscriptions across
      * user profiles.
      *
      * @return a SubscriptionManager that can see all subscriptions regardless its user profile
@@ -2008,13 +2017,12 @@
      * @see #getActiveSubscriptionInfoCount
      * @see UserHandle
      */
-    @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT)
+    @FlaggedApi(Flags.FLAG_ENFORCE_SUBSCRIPTION_USER_FILTER)
     // @RequiresPermission(TODO(b/308809058))
     // The permission check for accessing all subscriptions will be enforced upon calling the
     // individual APIs linked above.
     @NonNull public SubscriptionManager createForAllUserProfiles() {
-        mIsForAllUserProfiles = true;
-        return this;
+        return new SubscriptionManager(mContext, true/*isForAllUserProfiles*/);
     }
 
     /**
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
index fee1b25..2bc056e 100644
--- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -16,9 +16,6 @@
 
 package android.transparency.test.app;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
-
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -30,7 +27,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.internal.os.IBinaryTransparencyService.AppInfo;
 
 import org.junit.Before;
@@ -120,13 +116,7 @@
     @Test
     public void testCollectAllSilentInstalledMbaInfo() {
         // Action
-        var appInfoList =
-                ShellIdentityUtils.invokeMethodWithShellPermissions(
-                        mBt,
-                        (Bt) ->
-                                mBt.collectAllSilentInstalledMbaInfo(new Bundle()),
-                        QUERY_ALL_PACKAGES,
-                        INTERACT_ACROSS_USERS_FULL);
+        var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle());
 
         // Verify
         assertThat(appInfoList).isNotEmpty();  // because we just installed from the host side
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
new file mode 100644
index 0000000..9daba6a
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -0,0 +1,419 @@
+/*
+ * 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.server.vcn.routeselection;
+
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
+
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.net.IpSecTransformState;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.concurrent.TimeUnit;
+
+public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
+    private static final String TAG = IpSecPacketLossDetectorTest.class.getSimpleName();
+
+    private static final int REPLAY_BITMAP_LEN_BYTE = 512;
+    private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
+    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+    private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
+
+    @Mock private IpSecTransformWrapper mIpSecTransform;
+    @Mock private NetworkMetricMonitorCallback mMetricMonitorCallback;
+    @Mock private PersistableBundleWrapper mCarrierConfig;
+    @Mock private IpSecPacketLossDetector.Dependencies mDependencies;
+    @Spy private PacketLossCalculator mPacketLossCalculator = new PacketLossCalculator();
+
+    @Captor private ArgumentCaptor<OutcomeReceiver> mTransformStateReceiverCaptor;
+    @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+
+    private IpSecPacketLossDetector mIpSecPacketLossDetector;
+    private IpSecTransformState mTransformStateInitial;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mTransformStateInitial = newTransformState(0, 0, newReplayBitmap(0));
+
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+                .thenReturn((int) TimeUnit.MILLISECONDS.toSeconds(POLL_IPSEC_STATE_INTERVAL_MS));
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+
+        when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
+
+        mIpSecPacketLossDetector =
+                new IpSecPacketLossDetector(
+                        mVcnContext,
+                        mNetwork,
+                        mCarrierConfig,
+                        mMetricMonitorCallback,
+                        mDependencies);
+    }
+
+    private static IpSecTransformState newTransformState(
+            long rxSeqNo, long packtCount, byte[] replayBitmap) {
+        return new IpSecTransformState.Builder()
+                .setRxHighestSequenceNumber(rxSeqNo)
+                .setPacketCount(packtCount)
+                .setReplayBitmap(replayBitmap)
+                .build();
+    }
+
+    private static byte[] newReplayBitmap(int receivedPktCnt) {
+        final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
+        for (int i = 0; i < receivedPktCnt; i++) {
+            bitSet.set(i);
+        }
+        return Arrays.copyOf(bitSet.toByteArray(), REPLAY_BITMAP_LEN_BYTE);
+    }
+
+    private void verifyStopped() {
+        assertFalse(mIpSecPacketLossDetector.isStarted());
+        assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+
+        // No event scheduled
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testInitialization() throws Exception {
+        assertFalse(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+        verifyStopped();
+    }
+
+    private OutcomeReceiver<IpSecTransformState, RuntimeException>
+            startMonitorAndCaptureStateReceiver() {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        // Trigger the runnable
+        mTestLooper.dispatchAll();
+
+        verify(mIpSecTransform)
+                .getIpSecTransformState(any(), mTransformStateReceiverCaptor.capture());
+        return mTransformStateReceiverCaptor.getValue();
+    }
+
+    @Test
+    public void testStartMonitor() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+        assertTrue(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+        assertEquals(mIpSecTransform, mIpSecPacketLossDetector.getInboundTransformInternal());
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        // Verify the first polled state is stored
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+
+        // Verify next poll is scheduled
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNotNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testStartedMonitor_enterDozeMoze() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+        // Mock entering doze mode
+        final Intent intent = mock(Intent.class);
+        when(intent.getAction()).thenReturn(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        when(mPowerManagerService.isDeviceIdleMode()).thenReturn(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any(), any(), any());
+        final BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        broadcastReceiver.onReceive(mContext, intent);
+
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+    }
+
+    @Test
+    public void testStartedMonitor_updateInboundTransform() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+        // Update the inbound transform
+        final IpSecTransformWrapper newTransform = mock(IpSecTransformWrapper.class);
+        mIpSecPacketLossDetector.setInboundTransformInternal(newTransform);
+
+        // Verifications
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        mTestLooper.dispatchAll();
+        verify(newTransform).getIpSecTransformState(any(), any());
+    }
+
+    @Test
+    public void testStartedMonitor_updateCarrierConfig() throws Exception {
+        startMonitorAndCaptureStateReceiver();
+
+        final int additionalPollIntervalMs = (int) TimeUnit.SECONDS.toMillis(10L);
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+                .thenReturn(
+                        (int)
+                                TimeUnit.MILLISECONDS.toSeconds(
+                                        POLL_IPSEC_STATE_INTERVAL_MS + additionalPollIntervalMs));
+        mIpSecPacketLossDetector.setCarrierConfig(mCarrierConfig);
+        mTestLooper.dispatchAll();
+
+        // The already scheduled event is still fired with the old timeout
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        mTestLooper.dispatchAll();
+
+        // The next scheduled event will take 10 more seconds to fire
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(additionalPollIntervalMs);
+        assertNotNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testStopMonitor() throws Exception {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Unselect the monitor
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+        verifyStopped();
+    }
+
+    @Test
+    public void testClose() throws Exception {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Stop the monitor
+        mIpSecPacketLossDetector.close();
+        verifyStopped();
+        verify(mIpSecTransform).close();
+    }
+
+    @Test
+    public void testTransformStateReceiverOnResultWhenStopped() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        // Unselect the monitor
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+        verifyStopped();
+
+        xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+    }
+
+    @Test
+    public void testTransformStateReceiverOnError() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        xfrmStateReceiver.onError(new RuntimeException("Test"));
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+    }
+
+    private void checkHandleLossRate(
+            int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+            throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        doReturn(mockPacketLossRate)
+                .when(mPacketLossCalculator)
+                .getPacketLossRatePercentage(any(), any(), anyString());
+
+        // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
+        final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        xfrmStateReceiver.onResult(transformNew);
+
+        // Verifications
+        verify(mPacketLossCalculator)
+                .getPacketLossRatePercentage(
+                        eq(mTransformStateInitial), eq(transformNew), anyString());
+
+        if (isLastStateExpectedToUpdate) {
+            assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
+        } else {
+            assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+        }
+
+        if (isCallbackExpected) {
+            verify(mMetricMonitorCallback).onValidationResultReceived();
+        } else {
+            verify(mMetricMonitorCallback, never()).onValidationResultReceived();
+        }
+    }
+
+    @Test
+    public void testHandleLossRate_validationPass() throws Exception {
+        checkHandleLossRate(
+                2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+    }
+
+    @Test
+    public void testHandleLossRate_validationFail() throws Exception {
+        checkHandleLossRate(
+                22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+    }
+
+    @Test
+    public void testHandleLossRate_resultUnavalaible() throws Exception {
+        checkHandleLossRate(
+                PACKET_LOSS_UNAVALAIBLE,
+                false /* isLastStateExpectedToUpdate */,
+                false /* isCallbackExpected */);
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+            throws Exception {
+        assertEquals(
+                expectedLossRate,
+                mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState,
+            int rxSeqNo,
+            int packetCount,
+            int packetInWin,
+            int expectedDataLossRate)
+            throws Exception {
+        final IpSecTransformState newState =
+                newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+        checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
+    }
+
+    @Test
+    public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
+        checkGetPacketLossRate(
+                mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
+        checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+    }
+
+    @Test
+    public void testGetPacketLossRate_againstInitialState() throws Exception {
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 7001, 4096, 0);
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4096, 15);
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4000, 14);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_overlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+        checkGetPacketLossRate(oldState, 5000, 5001, 4096, 0);
+        checkGetPacketLossRate(oldState, 5000, 4000, 4096, 29);
+        checkGetPacketLossRate(oldState, 5000, 4000, 4000, 27);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_notOverlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+        checkGetPacketLossRate(oldState, 7000, 7001, 4096, 0);
+        checkGetPacketLossRate(oldState, 7000, 5000, 4096, 37);
+        checkGetPacketLossRate(oldState, 7000, 5000, 3000, 21);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_overlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+        checkGetPacketLossRate(oldState, 12000, 8096, 4096, 0);
+        checkGetPacketLossRate(oldState, 12000, 7000, 4096, 36);
+        checkGetPacketLossRate(oldState, 12000, 7000, 3000, 0);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_notOverlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+        checkGetPacketLossRate(oldState, 20000, 16096, 4096, 0);
+        checkGetPacketLossRate(oldState, 20000, 14000, 4096, 19);
+        checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index bf84bbe..355c221 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -20,6 +20,7 @@
 import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
 
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -29,7 +30,12 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.FeatureFlags;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
 import android.telephony.TelephonyManager;
 
@@ -90,32 +96,49 @@
 
     protected static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
 
+    @Mock protected Context mContext;
     @Mock protected Network mNetwork;
+    @Mock protected FeatureFlags mFeatureFlags;
+    @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
     @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     @Mock protected TelephonyManager mTelephonyManager;
+    @Mock protected IPowerManager mPowerManagerService;
 
     protected TestLooper mTestLooper;
     protected VcnContext mVcnContext;
+    protected PowerManager mPowerManager;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        final Context mockContext = mock(Context.class);
+        when(mNetwork.getNetId()).thenReturn(-1);
+
         mTestLooper = new TestLooper();
         mVcnContext =
                 spy(
                         new VcnContext(
-                                mockContext,
+                                mContext,
                                 mTestLooper.getLooper(),
                                 mock(VcnNetworkProvider.class),
                                 false /* isInTestMode */));
         doNothing().when(mVcnContext).ensureRunningOnLooperThread();
 
+        doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
+        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+
         setupSystemService(
-                mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+                mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
         when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
         when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
         when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
+
+        mPowerManager =
+                new PowerManager(
+                        mContext,
+                        mPowerManagerService,
+                        mock(IThermalService.class),
+                        mock(Handler.class));
+        setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index dbf2f51..d85c515 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -57,7 +57,7 @@
     private UnderlyingNetworkRecord mCellNetworkRecord;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         super.setUp();
 
         mWifiNetworkRecord = getTestNetworkRecord(WIFI_NETWORK_CAPABILITIES);
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
index a4567dd..985e70c 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -34,7 +34,7 @@
     private PersistableBundleWrapper mCarrierConfig;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         super.setUp();
         mCarrierConfig = new PersistableBundleWrapper(new PersistableBundle());
     }