Merge "Add the second toggle for the egress data permission on voice activation apps toggle page." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2ae72ef..c43aa75 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -75,6 +75,7 @@
     ":android.speech.flags-aconfig-java{.generated_srcjars}",
     ":power_flags_lib{.generated_srcjars}",
     ":android.content.flags-aconfig-java{.generated_srcjars}",
+    ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
 ]
 
 filegroup {
diff --git a/core/api/current.txt b/core/api/current.txt
index 15c054f..98b326f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18753,21 +18753,21 @@
     method @Nullable public java.security.Signature getSignature();
   }
 
-  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentListItem {
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentItem {
   }
 
-  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem {
-    ctor public PromptContentListItemBulletedText(@NonNull CharSequence);
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+    ctor public PromptContentItemBulletedText(@NonNull CharSequence);
     method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemBulletedText> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
   }
 
-  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem {
-    ctor public PromptContentListItemPlainText(@NonNull CharSequence);
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+    ctor public PromptContentItemPlainText(@NonNull CharSequence);
     method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemPlainText> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
   }
 
   @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView {
@@ -18776,7 +18776,7 @@
   @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
     method public int describeContents();
     method @Nullable public CharSequence getDescription();
-    method @NonNull public java.util.List<android.hardware.biometrics.PromptContentListItem> getListItems();
+    method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
     method public static int getMaxEachItemCharacterNumber();
     method public static int getMaxItemCount();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -18785,7 +18785,8 @@
 
   public static final class PromptVerticalListContentView.Builder {
     ctor public PromptVerticalListContentView.Builder();
-    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentListItem);
+    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem);
+    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int);
     method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
     method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence);
   }
@@ -23341,6 +23342,7 @@
     field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
     field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info";
     field public static final String KEY_HEIGHT = "height";
+    field @FlaggedApi("com.android.media.codec.flags.codec_importance") public static final String KEY_IMPORTANCE = "importance";
     field public static final String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
     field public static final String KEY_IS_ADTS = "is-adts";
     field public static final String KEY_IS_AUTOSELECT = "is-autoselect";
@@ -26669,12 +26671,16 @@
 
   public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns {
     method @Nullable public static String getVideoResolution(String);
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2; // 0x2
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1; // 0x1
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0; // 0x0
     field public static final String COLUMN_APP_LINK_COLOR = "app_link_color";
     field public static final String COLUMN_APP_LINK_ICON_URI = "app_link_icon_uri";
     field public static final String COLUMN_APP_LINK_INTENT_URI = "app_link_intent_uri";
     field public static final String COLUMN_APP_LINK_POSTER_ART_URI = "app_link_poster_art_uri";
     field public static final String COLUMN_APP_LINK_TEXT = "app_link_text";
     field public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
     field public static final String COLUMN_BROWSABLE = "browsable";
     field public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id";
     field public static final String COLUMN_DESCRIPTION = "description";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d26662d..b8c9fc66 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -622,6 +622,7 @@
     field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
     field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+    field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = "android:access_restricted_settings";
     field public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
     field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
     field public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -3541,6 +3542,7 @@
     field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
+    field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
     field public static final String ETHERNET_SERVICE = "ethernet";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
     field public static final String FONT_SERVICE = "font";
@@ -14554,6 +14556,7 @@
     field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1
     field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9
     field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41; // 0x29
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10
     field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12
@@ -14600,6 +14603,10 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener {
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
   public static interface TelephonyCallback.SrvccStateListener {
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int);
   }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index ebb5ba0..1fd49ef 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3574,7 +3574,7 @@
          * foreground.  This may be running a window that is behind the current
          * foreground (so paused and with its state saved, not interacting with
          * the user, but visible to them to some degree); it may also be running
-         * other services under the system's control that it inconsiders important.
+         * other services under the system's control that it considers important.
          */
         public static final int IMPORTANCE_VISIBLE = 200;
 
@@ -3646,9 +3646,9 @@
         public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
 
         /**
-         * Constant for {@link #importance}: This process process contains
-         * cached code that is expendable, not actively running any app components
-         * we care about.
+         * Constant for {@link #importance}: This process contains cached code
+         * that is expendable, not actively running any app components we care
+         * about.
          */
         public static final int IMPORTANCE_CACHED = 400;
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1db1caf..669baf9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2180,6 +2180,8 @@
      *
      * @hide
      */
+    @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+    @SystemApi
     public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
             "android:access_restricted_settings";
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9cf732a..d755413 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -31,6 +31,7 @@
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
 import android.app.contentsuggestions.ContentSuggestionsManager;
 import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
 import android.app.job.JobSchedulerFrameworkInitializer;
 import android.app.people.PeopleManager;
 import android.app.prediction.AppPredictionManager;
@@ -1631,6 +1632,9 @@
             OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
             DeviceLockFrameworkInitializer.registerServiceWrappers();
             VirtualizationFrameworkInitializer.registerServiceWrappers();
+            if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+                EnhancedConfirmationFrameworkInitializer.registerServiceWrappers();
+            }
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4bc1237..249c0e43 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4242,6 +4242,7 @@
             VIRTUALIZATION_SERVICE,
             GRAMMATICAL_INFLECTION_SERVICE,
             SECURITY_STATE_SERVICE,
+           //@hide: ECM_ENHANCED_CONFIRMATION_SERVICE,
 
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -6527,6 +6528,18 @@
     public static final String SECURITY_STATE_SERVICE = "security_state";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.app.ecm.EnhancedConfirmationManager}.
+     *
+     * @see #getSystemService(String)
+     * @see android.app.ecm.EnhancedConfirmationManager
+     * @hide
+     */
+    @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+    @SystemApi
+    public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index f876eeb..1165f9a 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -33,4 +33,11 @@
     name: "new_settings_ui"
     description: "Enables new settings UI for VIC"
     bug: "315209085"
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "selector_ui_improvements_enabled"
+    description: "Enables Credential Selector UI improvements for VIC"
+    bug: "319448437"
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/core/java/android/hardware/biometrics/PromptContentItem.java
similarity index 88%
rename from core/java/android/hardware/biometrics/PromptContentListItem.java
rename to core/java/android/hardware/biometrics/PromptContentItem.java
index fa3783d..c47b37a 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItem.java
+++ b/core/java/android/hardware/biometrics/PromptContentItem.java
@@ -21,9 +21,9 @@
 import android.annotation.FlaggedApi;
 
 /**
- * A list item shown on {@link PromptVerticalListContentView}.
+ * An item shown on {@link PromptContentView}.
  */
 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public interface PromptContentListItem {
+public interface PromptContentItem {
 }
 
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
similarity index 75%
rename from core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java
rename to core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
index c31f8a6..c5e5a80 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -27,7 +27,7 @@
  * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
  */
 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public final class PromptContentListItemBulletedText implements PromptContentListItemParcelable {
+public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
     private final CharSequence mText;
 
     /**
@@ -35,7 +35,7 @@
      *
      * @param text The text of this list item.
      */
-    public PromptContentListItemBulletedText(@NonNull CharSequence text) {
+    public PromptContentItemBulletedText(@NonNull CharSequence text) {
         mText = text;
     }
 
@@ -67,15 +67,15 @@
      * @see Parcelable.Creator
      */
     @NonNull
-    public static final Creator<PromptContentListItemBulletedText> CREATOR = new Creator<>() {
+    public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() {
         @Override
-        public PromptContentListItemBulletedText createFromParcel(Parcel in) {
-            return new PromptContentListItemBulletedText(in.readCharSequence());
+        public PromptContentItemBulletedText createFromParcel(Parcel in) {
+            return new PromptContentItemBulletedText(in.readCharSequence());
         }
 
         @Override
-        public PromptContentListItemBulletedText[] newArray(int size) {
-            return new PromptContentListItemBulletedText[size];
+        public PromptContentItemBulletedText[] newArray(int size) {
+            return new PromptContentItemBulletedText[size];
         }
     };
 }
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
similarity index 79%
rename from core/java/android/hardware/biometrics/PromptContentListItemParcelable.java
rename to core/java/android/hardware/biometrics/PromptContentItemParcelable.java
index 15271f0..668912cf 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
@@ -22,9 +22,9 @@
 import android.os.Parcelable;
 
 /**
- * A parcelable {@link PromptContentListItem}.
+ * A parcelable {@link PromptContentItem}.
  */
 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-sealed interface PromptContentListItemParcelable extends PromptContentListItem, Parcelable
-        permits PromptContentListItemPlainText, PromptContentListItemBulletedText {
+sealed interface PromptContentItemParcelable extends PromptContentItem, Parcelable
+        permits PromptContentItemPlainText, PromptContentItemBulletedText {
 }
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
similarity index 76%
rename from core/java/android/hardware/biometrics/PromptContentListItemPlainText.java
rename to core/java/android/hardware/biometrics/PromptContentItemPlainText.java
index d72a758..6434c59 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -27,7 +27,7 @@
  * A list item with plain text shown on {@link PromptVerticalListContentView}.
  */
 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public final class PromptContentListItemPlainText implements PromptContentListItemParcelable {
+public final class PromptContentItemPlainText implements PromptContentItemParcelable {
     private final CharSequence mText;
 
     /**
@@ -35,7 +35,7 @@
      *
      * @param text The text of this list item.
      */
-    public PromptContentListItemPlainText(@NonNull CharSequence text) {
+    public PromptContentItemPlainText(@NonNull CharSequence text) {
         mText = text;
     }
 
@@ -67,15 +67,15 @@
      * @see Parcelable.Creator
      */
     @NonNull
-    public static final Creator<PromptContentListItemPlainText> CREATOR = new Creator<>() {
+    public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() {
         @Override
-        public PromptContentListItemPlainText createFromParcel(Parcel in) {
-            return new PromptContentListItemPlainText(in.readCharSequence());
+        public PromptContentItemPlainText createFromParcel(Parcel in) {
+            return new PromptContentItemPlainText(in.readCharSequence());
         }
 
         @Override
-        public PromptContentListItemPlainText[] newArray(int size) {
-            return new PromptContentListItemPlainText[size];
+        public PromptContentItemPlainText[] newArray(int size) {
+            return new PromptContentItemPlainText[size];
         }
     };
 }
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index f3cb189..f3e6290 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -40,9 +40,9 @@
  *     .setSubTitle(...)
  *     .setContentView(new PromptVerticalListContentView.Builder()
  *         .setDescription("test description")
- *         .addListItem(new PromptContentListItemPlainText("test item 1"))
- *         .addListItem(new PromptContentListItemPlainText("test item 2"))
- *         .addListItem(new PromptContentListItemBulletedText("test item 3"))
+ *         .addListItem(new PromptContentItemPlainText("test item 1"))
+ *         .addListItem(new PromptContentItemPlainText("test item 2"))
+ *         .addListItem(new PromptContentItemBulletedText("test item 3"))
  *         .build())
  *     .build();
  * </pre>
@@ -51,11 +51,11 @@
 public final class PromptVerticalListContentView implements PromptContentViewParcelable {
     private static final int MAX_ITEM_NUMBER = 20;
     private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
-    private final List<PromptContentListItemParcelable> mContentList;
+    private final List<PromptContentItemParcelable> mContentList;
     private final CharSequence mDescription;
 
     private PromptVerticalListContentView(
-            @NonNull List<PromptContentListItemParcelable> contentList,
+            @NonNull List<PromptContentItemParcelable> contentList,
             @NonNull CharSequence description) {
         mContentList = contentList;
         mDescription = description;
@@ -63,8 +63,8 @@
 
     private PromptVerticalListContentView(Parcel in) {
         mContentList = in.readArrayList(
-                PromptContentListItemParcelable.class.getClassLoader(),
-                PromptContentListItemParcelable.class);
+                PromptContentItemParcelable.class.getClassLoader(),
+                PromptContentItemParcelable.class);
         mDescription = in.readCharSequence();
     }
 
@@ -94,13 +94,13 @@
     }
 
     /**
-     * Gets the list of ListItem on the content view, as set by
-     * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentListItem)}.
+     * Gets the list of items on the content view, as set by
+     * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentItem)}.
      *
      * @return The item list on the content view.
      */
     @NonNull
-    public List<PromptContentListItem> getListItems() {
+    public List<PromptContentItem> getListItems() {
         return new ArrayList<>(mContentList);
     }
 
@@ -142,7 +142,7 @@
      * A builder that collects arguments to be shown on the vertical list view.
      */
     public static final class Builder {
-        private final List<PromptContentListItemParcelable> mContentList = new ArrayList<>();
+        private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
         private CharSequence mDescription;
 
         /**
@@ -159,28 +159,50 @@
 
         /**
          * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
-         * total.
+         * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+         * characters.
          *
          * @param listItem The list item view to display
          * @return This builder.
          */
         @NonNull
-        public Builder addListItem(@NonNull PromptContentListItem listItem) {
+        public Builder addListItem(@NonNull PromptContentItem listItem) {
             if (doesListItemExceedsCharLimit(listItem)) {
                 throw new IllegalStateException(
                         "The character number of list item exceeds "
                                 + MAX_EACH_ITEM_CHARACTER_NUMBER);
             }
-            mContentList.add((PromptContentListItemParcelable) listItem);
+            mContentList.add((PromptContentItemParcelable) listItem);
             return this;
         }
 
-        private boolean doesListItemExceedsCharLimit(PromptContentListItem listItem) {
-            if (listItem instanceof PromptContentListItemPlainText) {
-                return ((PromptContentListItemPlainText) listItem).getText().length()
+
+        /**
+         * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
+         * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+         * characters.
+         *
+         * @param listItem The list item view to display
+         * @param index The position at which to add the item
+         * @return This builder.
+         */
+        @NonNull
+        public Builder addListItem(@NonNull PromptContentItem listItem, int index) {
+            if (doesListItemExceedsCharLimit(listItem)) {
+                throw new IllegalStateException(
+                        "The character number of list item exceeds "
+                                + MAX_EACH_ITEM_CHARACTER_NUMBER);
+            }
+            mContentList.add(index, (PromptContentItemParcelable) listItem);
+            return this;
+        }
+
+        private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) {
+            if (listItem instanceof PromptContentItemPlainText) {
+                return ((PromptContentItemPlainText) listItem).getText().length()
                         > MAX_EACH_ITEM_CHARACTER_NUMBER;
-            } else if (listItem instanceof PromptContentListItemBulletedText) {
-                return ((PromptContentListItemBulletedText) listItem).getText().length()
+            } else if (listItem instanceof PromptContentItemBulletedText) {
+                return ((PromptContentItemBulletedText) listItem).getText().length()
                         > MAX_EACH_ITEM_CHARACTER_NUMBER;
             } else {
                 return false;
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index bced217..39b6aeb 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -45,7 +45,8 @@
 }
 
 flag {
-    name: "enhanced_confirmation_mode_apis"
+    name: "enhanced_confirmation_mode_apis_enabled"
+    is_fixed_read_only: true
     namespace: "permissions"
     description: "enable enhanced confirmation mode apis"
     bug: "310220212"
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index adc54f5..f2bdbf6 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -325,7 +325,7 @@
             Slog.v(TAG, "BinderCallback#onQueryDetected");
             Binder.withCleanCallingIdentity(() -> {
                 synchronized (mLock) {
-                    mCallback.onQueryDetected(partialQuery);
+                    mExecutor.execute(()->mCallback.onQueryDetected(partialQuery));
                 }
             });
         }
@@ -335,7 +335,7 @@
             Slog.v(TAG, "BinderCallback#onQueryFinished");
             Binder.withCleanCallingIdentity(() -> {
                 synchronized (mLock) {
-                    mCallback.onQueryFinished();
+                    mExecutor.execute(()->mCallback.onQueryFinished());
                 }
             });
         }
@@ -345,7 +345,7 @@
             Slog.v(TAG, "BinderCallback#onQueryRejected");
             Binder.withCleanCallingIdentity(() -> {
                 synchronized (mLock) {
-                    mCallback.onQueryRejected();
+                    mExecutor.execute(()->mCallback.onQueryRejected());
                 }
             });
         }
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index cf1156d..fb57921 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1681,6 +1681,10 @@
                 @EmergencyCallbackModeStopReason int reason) {
             // not support. Can't override. Use TelephonyCallback.
         }
+
+        public final void onSimultaneousCallingStateChanged(int[] subIds) {
+            // not supported on the deprecated interface - Use TelephonyCallback instead
+        }
     }
 
     private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 19bcf28..dc6a035 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -33,15 +34,19 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.flags.Flags;
 
 import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 /**
  * A callback class for monitoring changes in specific telephony states
@@ -627,6 +632,18 @@
     public static final int EVENT_EMERGENCY_CALLBACK_MODE_CHANGED = 40;
 
     /**
+     * Event for listening to changes in simultaneous cellular calling subscriptions.
+     *
+     * @see SimultaneousCellularCallingSupportListener
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41;
+
+    /**
      * @hide
      */
     @IntDef(prefix = {"EVENT_"}, value = {
@@ -669,7 +686,8 @@
             EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
             EVENT_TRIGGER_NOTIFY_ANBR,
             EVENT_MEDIA_QUALITY_STATUS_CHANGED,
-            EVENT_EMERGENCY_CALLBACK_MODE_CHANGED
+            EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
+            EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TelephonyEvent {
@@ -1373,6 +1391,44 @@
     }
 
     /**
+     * Interface for listening to changes in the simultaneous cellular calling state for active
+     * cellular subscriptions.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    @SystemApi
+    public interface SimultaneousCellularCallingSupportListener {
+        /**
+         * Notify the Listener that the subscriptions available for simultaneous <b>cellular</b>
+         * calling have changed.
+         * <p>
+         * If we have an ongoing <b>cellular</b> call on one subscription in this Set, a
+         * simultaneous incoming or outgoing <b>cellular</b> call is possible on any of the
+         * subscriptions in this Set. On a traditional Dual Sim Dual Standby device, simultaneous
+         * calling is not possible between subscriptions, where on a Dual Sim Dual Active device,
+         * simultaneous calling may be possible between subscriptions in certain network conditions.
+         * <p>
+         * Note: This listener only tracks the capability of the modem to perform simultaneous
+         * cellular calls and does not track the simultaneous calling state of scenarios based on
+         * multiple IMS registration over multiple transports (WiFi/Internet calling).
+         * <p>
+         * Note: This listener fires for all changes to cellular calling subscriptions independent
+         * of which subscription it is registered on.
+         *
+         * @param simultaneousCallingSubscriptionIds The Set of subscription IDs that support
+         * simultaneous calling. If there is an ongoing call on a subscription in this Set, then a
+         * simultaneous incoming or outgoing call is only possible for other subscriptions in this
+         * Set. If there is an ongoing call on a subscription that is not in this Set, then
+         * simultaneous calling is not possible at the current time.
+         *
+         */
+        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+        void onSimultaneousCellularCallingSubscriptionsChanged(
+                @NonNull Set<Integer> simultaneousCallingSubscriptionIds);
+    }
+
+    /**
      * Interface for call attributes listener.
      *
      * @hide
@@ -1976,6 +2032,17 @@
                                     allowedNetworkType)));
         }
 
+        public void onSimultaneousCallingStateChanged(int[] subIds) {
+            SimultaneousCellularCallingSupportListener listener =
+                    (SimultaneousCellularCallingSupportListener) mTelephonyCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> listener.onSimultaneousCellularCallingSubscriptionsChanged(
+                                    Arrays.stream(subIds).boxed().collect(Collectors.toSet()))));
+        }
+
         public void onLinkCapacityEstimateChanged(
                 List<LinkCapacityEstimate> linkCapacityEstimateList) {
             LinkCapacityEstimateChangedListener listener =
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 886727e..0de4505 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -994,6 +994,21 @@
         }
     }
 
+    /**
+     * Notify external listeners that the subscriptions supporting simultaneous cellular calling
+     * have changed.
+     * @param subIds The new set of subIds supporting simultaneous cellular calling.
+     */
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(Set<Integer> subIds) {
+        try {
+            sRegistry.notifySimultaneousCellularCallingSubscriptionsChanged(
+                    subIds.stream().mapToInt(i -> i).toArray());
+        } catch (RemoteException ex) {
+            // system server crash
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     public @NonNull Set<Integer> getEventsFromCallback(
             @NonNull TelephonyCallback telephonyCallback) {
         Set<Integer> eventList = new ArraySet<>();
@@ -1135,7 +1150,11 @@
             eventList.add(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
         }
 
-
+        if (telephonyCallback
+                instanceof TelephonyCallback.SimultaneousCellularCallingSupportListener) {
+            eventList.add(
+                    TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
+        }
         return eventList;
     }
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 36b74e3..7903050 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -69,12 +69,13 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.AddToSurfaceSyncGroupResult;
+import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
-import android.window.ScreenCapture;
-import android.window.WindowContextInfo;
 import android.window.ITrustedPresentationListener;
+import android.window.ScreenCapture;
 import android.window.TrustedPresentationThresholds;
+import android.window.WindowContextInfo;
 
 /**
  * System private interface to the window manager.
@@ -1083,4 +1084,8 @@
 
 
     void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+
+    boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+
+    void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 257ecc5..c98d1d7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15015,6 +15015,7 @@
     }
 
     /** @hide */
+    @Nullable
     View getSelfOrParentImportantForA11y() {
         if (isImportantForAccessibility()) return this;
         ViewParent parent = getParentForAccessibility();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c66f3c8..34b46700 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -87,6 +87,7 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
 import static android.view.accessibility.Flags.forceInvertColor;
 import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -11509,6 +11510,15 @@
                 event.setContentChangeTypes(mChangeTypes);
                 if (mAction.isPresent()) event.setAction(mAction.getAsInt());
                 if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
+
+                if (fixMergedContentChangeEvent()) {
+                    if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+                        final View importantParent = source.getSelfOrParentImportantForA11y();
+                        if (importantParent != null) {
+                            source = importantParent;
+                        }
+                    }
+                }
                 source.sendAccessibilityEventUnchecked(event);
             } else {
                 mLastEventTimeMillis = 0;
@@ -11543,14 +11553,29 @@
             }
 
             if (mSource != null) {
-                // If there is no common predecessor, then mSource points to
-                // a removed view, hence in this case always prefer the source.
-                View predecessor = getCommonPredecessor(mSource, source);
-                if (predecessor != null) {
-                    predecessor = predecessor.getSelfOrParentImportantForA11y();
+                if (fixMergedContentChangeEvent()) {
+                    View newSource = getCommonPredecessor(mSource, source);
+                    if (newSource == null) {
+                        // If there is no common predecessor, then mSource points to
+                        // a removed view, hence in this case always prefer the source.
+                        newSource = source;
+                    }
+
+                    mChangeTypes |= changeType;
+                    if (mSource != newSource) {
+                        mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+                        mSource = newSource;
+                    }
+                } else {
+                    // If there is no common predecessor, then mSource points to
+                    // a removed view, hence in this case always prefer the source.
+                    View predecessor = getCommonPredecessor(mSource, source);
+                    if (predecessor != null) {
+                        predecessor = predecessor.getSelfOrParentImportantForA11y();
+                    }
+                    mSource = (predecessor != null) ? predecessor : source;
+                    mChangeTypes |= changeType;
                 }
-                mSource = (predecessor != null) ? predecessor : source;
-                mChangeTypes |= changeType;
 
                 final int performingAction = mAccessibilityManager.getPerformingAction();
                 if (performingAction != 0) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c7355c1..efae57c 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -53,6 +53,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "fix_merged_content_change_event"
+    description: "Fixes event type and source of content change event merged in ViewRootImpl"
+    bug: "277305460"
+}
+
+flag {
+    namespace: "accessibility"
     name: "flash_notification_system_api"
     description: "Makes flash notification APIs as system APIs for calling from mainline module"
     bug: "303131332"
diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/core/java/android/window/IScreenRecordingCallback.aidl
similarity index 61%
copy from core/java/android/hardware/biometrics/PromptContentListItem.java
copy to core/java/android/window/IScreenRecordingCallback.aidl
index fa3783d..560ee75 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItem.java
+++ b/core/java/android/window/IScreenRecordingCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,11 @@
  * limitations under the License.
  */
 
-package android.hardware.biometrics;
-
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
+package android.window;
 
 /**
- * A list item shown on {@link PromptVerticalListContentView}.
+ * @hide
  */
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public interface PromptContentListItem {
+oneway interface IScreenRecordingCallback {
+    void onScreenRecordingStateChanged(boolean visibleInScreenRecording);
 }
-
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 3c3c846..751c1a8 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -80,3 +80,11 @@
     is_fixed_read_only: true
     bug: "278757236"
 }
+
+flag {
+    namespace: "window_surfaces"
+    name: "screen_recording_callbacks"
+    description: "Enable screen recording callbacks public API"
+    is_fixed_read_only: true
+    bug: "304574518"
+}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 03cfd4f..969f95d 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -80,4 +80,5 @@
     void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus);
     void onCallBackModeStarted(int type);
     void onCallBackModeStopped(int type, int reason);
+    void onSimultaneousCallingStateChanged(in int[] subIds);
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index aab2242..0203ea4 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -104,6 +104,7 @@
     void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
     void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
             in List<LinkCapacityEstimate> linkCapacityEstimateList);
+    void notifySimultaneousCellularCallingSubscriptionsChanged(in int[] subIds);
 
     void addCarrierPrivilegesCallback(
             int phoneId, ICarrierPrivilegesCallback callback, String pkg, String featureId);
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 5b95ee7..f6fe3dd 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -61,13 +61,12 @@
                           static_cast<int32_t>(HAL_PIXEL_FORMAT_RGBA_FP16)) !=
                         i.pixelFormats.end() &&
                 std::find(i.standards.begin(), i.standards.end(),
-                          static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT2020)) !=
+                          static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT709)) !=
                         i.standards.end() &&
                 std::find(i.transfers.begin(), i.transfers.end(),
-                          static_cast<int32_t>(HAL_DATASPACE_TRANSFER_ST2084)) !=
-                        i.transfers.end() &&
+                          static_cast<int32_t>(HAL_DATASPACE_TRANSFER_SRGB)) != i.transfers.end() &&
                 std::find(i.ranges.begin(), i.ranges.end(),
-                          static_cast<int32_t>(HAL_DATASPACE_RANGE_FULL)) != i.ranges.end()) {
+                          static_cast<int32_t>(HAL_DATASPACE_RANGE_EXTENDED)) != i.ranges.end()) {
                 return true;
             }
         }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 28adccd..5be29a6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -57,7 +57,6 @@
         <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_oem_satellite</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
@@ -103,7 +102,6 @@
     <string translatable="false" name="status_bar_call_strength">call_strength</string>
     <string translatable="false" name="status_bar_sensors_off">sensors_off</string>
     <string translatable="false" name="status_bar_screen_record">screen_record</string>
-    <string translatable="false" name="status_bar_oem_satellite">satellite</string>
 
     <!-- Flag indicating whether the surface flinger has limited
          alpha compositing functionality in hardware.  If set, the window
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ef12d8f..d12ef2b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3198,7 +3198,6 @@
   <java-symbol type="string" name="status_bar_camera" />
   <java-symbol type="string" name="status_bar_sensors_off" />
   <java-symbol type="string" name="status_bar_screen_record" />
-  <java-symbol type="string" name="status_bar_oem_satellite" />
 
   <!-- Locale picker -->
   <java-symbol type="id" name="locale_search_menu" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 917a300..3a778c3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -535,6 +535,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1583619037": {
+      "message": "Failed to register MediaProjectionWatcherCallback",
+      "level": "ERROR",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/ScreenRecordingCallbackController.java"
+    },
     "-1582845629": {
       "message": "Starting animation on %s",
       "level": "VERBOSE",
@@ -2983,12 +2989,6 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "466506262": {
-      "message": "Clear freezing of %s: visible=%b freezing=%b",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "485170982": {
       "message": "Not finishing noHistory %s on stop because we're just sleeping",
       "level": "DEBUG",
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index ddae673..b21bf11 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -23,9 +23,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.app.ActivityThread;
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
@@ -43,15 +41,6 @@
  * line-break property</a> for more information.
  */
 public final class LineBreakConfig implements Parcelable {
-
-    /**
-     * A feature ID for automatic line break word style.
-     * @hide
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    public static final long WORD_STYLE_AUTO = 280005585L;
-
     /**
      * No hyphenation preference is specified.
      *
@@ -487,8 +476,15 @@
      * @hide
      */
     public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
-        final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
-                ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE;
+        final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+                .targetSdkVersion;
+        final int defaultStyle;
+        final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        if (targetSdkVersion >= vicVersion) {
+            defaultStyle = LINE_BREAK_STYLE_AUTO;
+        } else {
+            defaultStyle = LINE_BREAK_STYLE_NONE;
+        }
         if (config == null) {
             return defaultStyle;
         }
@@ -515,8 +511,15 @@
      */
     public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
             @Nullable LineBreakConfig config) {
-        final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
-                ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE;
+        final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+                .targetSdkVersion;
+        final int defaultWordStyle;
+        final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        if (targetSdkVersion >= vicVersion) {
+            defaultWordStyle = LINE_BREAK_WORD_STYLE_AUTO;
+        } else {
+            defaultWordStyle = LINE_BREAK_WORD_STYLE_NONE;
+        }
         if (config == null) {
             return defaultWordStyle;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1211451..bd8ce80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
 import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -74,10 +75,6 @@
     private DismissView mDismissView;
     private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
-    // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
-    /** Whether the expanded view is displaying on the left of the screen or not. */
-    private boolean mOnLeft = false;
-
     /** Whether a bubble is expanded. */
     private boolean mIsExpanded = false;
 
@@ -154,10 +151,10 @@
         return mIsExpanded;
     }
 
-    // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done.
+    // TODO(b/313661121) - when dragging is implemented, check user setting first
     /** Whether the expanded view is positioned on the left or right side of the screen. */
     public boolean isOnLeft() {
-        return mOnLeft;
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
     }
 
     /** Shows the expanded view of the provided bubble. */
@@ -216,7 +213,7 @@
                         return Unit.INSTANCE;
                     });
 
-            addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
+            addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
         }
 
         if (mEducationViewController.isEducationVisible()) {
@@ -311,7 +308,7 @@
         lp.width = width;
         lp.height = height;
         mExpandedView.setLayoutParams(lp);
-        if (mOnLeft) {
+        if (isOnLeft()) {
             mExpandedView.setX(mPositioner.getInsets().left + padding);
         } else {
             mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index eebf8aa..b40b73c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -716,7 +716,6 @@
     ],
     shared_libs: [
         "libmemunreachable",
-        "server_configurable_flags",
     ],
     srcs: [
         "tests/unit/main.cpp",
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index d4af087..a5a841e 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -23,6 +23,7 @@
 
 #include <mutex>
 
+#include "Properties.h"
 #include "utils/Macros.h"
 
 namespace android {
@@ -60,7 +61,13 @@
     static void setWideColorDataspace(ADataSpace dataspace);
 
     static void setSupportFp16ForHdr(bool supportFp16ForHdr);
-    static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; };
+    static bool isSupportFp16ForHdr() {
+        if (!Properties::hdr10bitPlus) {
+            return false;
+        }
+
+        return get()->mSupportFp16ForHdr;
+    };
 
     static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
     static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 16de21d..71f7926 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -379,7 +379,7 @@
         case kAlpha_8_SkColorType:
             formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support();
             formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM;
-            formatInfo.format = GL_R8;
+            formatInfo.format = GL_RED;
             formatInfo.type = GL_UNSIGNED_BYTE;
             formatInfo.vkFormat = VK_FORMAT_R8_UNORM;
             break;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d58c872..755332ff 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -38,6 +38,9 @@
 constexpr bool clip_surfaceviews() {
     return false;
 }
+constexpr bool hdr_10bit_plus() {
+    return false;
+}
 }  // namespace hwui_flags
 #endif
 
@@ -105,6 +108,7 @@
 float Properties::maxHdrHeadroomOn8bit = 5.f;  // TODO: Refine this number
 
 bool Properties::clipSurfaceViews = false;
+bool Properties::hdr10bitPlus = false;
 
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
@@ -177,6 +181,7 @@
 
     clipSurfaceViews =
             base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+    hdr10bitPlus = hwui_flags::hdr_10bit_plus();
 
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index b956fac..ec53070 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -336,6 +336,7 @@
     static float maxHdrHeadroomOn8bit;
 
     static bool clipSurfaceViews;
+    static bool hdr10bitPlus;
 
     static StretchEffectBehavior getStretchEffectBehavior() {
         return stretchEffectBehavior;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index e0f1f6e..326b6ed 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -650,9 +650,14 @@
             mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
             break;
         case ColorMode::Hdr:
-            mSurfaceColorType = SkColorType::kN32_SkColorType;
-            mSurfaceColorSpace = SkColorSpace::MakeRGB(
-                    GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+            if (DeviceInfo::get()->isSupportFp16ForHdr()) {
+                mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
+                mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+            } else {
+                mSurfaceColorType = SkColorType::kN32_SkColorType;
+                mSurfaceColorSpace = SkColorSpace::MakeRGB(
+                        GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+            }
             break;
         case ColorMode::Hdr10:
             mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
@@ -669,8 +674,13 @@
 void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
     if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
         mTargetSdrHdrRatio = ratio;
-        mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
-                                                   SkNamedGamut::kDisplayP3);
+
+        if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) {
+            mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+        } else {
+            mSurfaceColorSpace = SkColorSpace::MakeRGB(
+                    GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+        }
     } else {
         mTargetSdrHdrRatio = 1.f;
     }
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index facf30b..2904dfe 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -441,22 +441,32 @@
             colorMode = ColorMode::Default;
         }
 
-        if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
+        // TODO: maybe we want to get rid of the WCG check if overlay properties just works?
+        const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
+                                DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
+
+        if (canUseFp16) {
             if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
                 colorMode = ColorMode::Default;
             } else {
                 config = mEglConfigF16;
             }
         }
+
         if (EglExtensions.glColorSpace) {
             attribs[0] = EGL_GL_COLORSPACE_KHR;
             switch (colorMode) {
                 case ColorMode::Default:
                     attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
                     break;
+                case ColorMode::Hdr:
+                    if (canUseFp16) {
+                        attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+                        break;
+                        // No fp16 support so fallthrough to HDR10
+                    }
                 // We don't have an EGL colorspace for extended range P3 that's used for HDR
                 // So override it after configuring the EGL context
-                case ColorMode::Hdr:
                 case ColorMode::Hdr10:
                     overrideWindowDataSpaceForHdr = true;
                     attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 46db777..587e35b 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -16,6 +16,9 @@
 
 package android.media;
 
+import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1635,6 +1638,34 @@
      */
     public static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop";
 
+    /**
+     * A key describing the desired codec importance for the application.
+     * <p>
+     * The associated value is a positive integer including zero.
+     * Higher value means lesser importance.
+     * <p>
+     * The resource manager may use the codec importance, along with other factors
+     * when reclaiming codecs from an application.
+     * The specifics of reclaim policy is device dependent, but specifying the codec importance,
+     * will allow the resource manager to prioritize reclaiming less important codecs
+     * (assigned higher values) from the (reclaim) requesting application first.
+     * So, the codec importance is only relevant within the context of that application.
+     * <p>
+     * The codec importance can be set:
+     * <ul>
+     * <li>through {@link MediaCodec#configure}. </li>
+     * <li>through {@link MediaCodec#setParameters} if the codec has been configured already,
+     * which allows the users to change the codec importance multiple times.
+     * </ul>
+     * Any change/update in codec importance is guaranteed upon the completion of the function call
+     * that sets the codec importance. So, in case of concurrent codec operations,
+     * make sure to wait for the change in codec importance, before using another codec.
+     * Note that unless specified, by default the codecs will have highest importance (of value 0).
+     *
+     */
+    @FlaggedApi(FLAG_CODEC_IMPORTANCE)
+    public static final String KEY_IMPORTANCE = "importance";
+
     /* package private */ MediaFormat(@NonNull Map<String, Object> map) {
         mMap = map;
     }
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 8ce1b6d..3d927d3 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -121,7 +121,7 @@
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
                 + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void addCallback(IMediaProjectionWatcherCallback callback);
+    MediaProjectionInfo addCallback(IMediaProjectionWatcherCallback callback);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
diff --git a/media/java/android/media/projection/MediaProjectionInfo.java b/media/java/android/media/projection/MediaProjectionInfo.java
index ff60856..c820392 100644
--- a/media/java/android/media/projection/MediaProjectionInfo.java
+++ b/media/java/android/media/projection/MediaProjectionInfo.java
@@ -16,6 +16,7 @@
 
 package android.media.projection;
 
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -26,15 +27,18 @@
 public final class MediaProjectionInfo implements Parcelable {
     private final String mPackageName;
     private final UserHandle mUserHandle;
+    private final IBinder mLaunchCookie;
 
-    public MediaProjectionInfo(String packageName, UserHandle handle) {
+    public MediaProjectionInfo(String packageName, UserHandle handle, IBinder launchCookie) {
         mPackageName = packageName;
         mUserHandle = handle;
+        mLaunchCookie = launchCookie;
     }
 
     public MediaProjectionInfo(Parcel in) {
         mPackageName = in.readString();
         mUserHandle = UserHandle.readFromParcel(in);
+        mLaunchCookie = in.readStrongBinder();
     }
 
     public String getPackageName() {
@@ -45,12 +49,16 @@
         return mUserHandle;
     }
 
+    public IBinder getLaunchCookie() {
+        return mLaunchCookie;
+    }
+
     @Override
     public boolean equals(Object o) {
-        if (o instanceof MediaProjectionInfo) {
-            final MediaProjectionInfo other = (MediaProjectionInfo) o;
+        if (o instanceof MediaProjectionInfo other) {
             return Objects.equals(other.mPackageName, mPackageName)
-                    && Objects.equals(other.mUserHandle, mUserHandle);
+                    && Objects.equals(other.mUserHandle, mUserHandle)
+                    && Objects.equals(other.mLaunchCookie, mLaunchCookie);
         }
         return false;
     }
@@ -64,7 +72,8 @@
     public String toString() {
         return "MediaProjectionInfo{mPackageName="
             + mPackageName + ", mUserHandle="
-            + mUserHandle + "}";
+            + mUserHandle + ", mLaunchCookie"
+            + mLaunchCookie + "}";
     }
 
     @Override
@@ -76,6 +85,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(mPackageName);
         UserHandle.writeToParcel(mUserHandle, out);
+        out.writeStrongBinder(mLaunchCookie);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<MediaProjectionInfo> CREATOR =
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index db01950..7f8f1a3 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,6 +30,7 @@
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
+import android.media.tv.flags.Flags;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -2540,9 +2542,9 @@
          * <p>This is used to indicate the broadcast visibility type defined in the underlying
          * broadcast standard or country/operator profile, if applicable. For example,
          * {@code visible_service_flag} and {@code numeric_selection_flag} of
-         * {@code service_attribute_descriptor} in D-Book, {@code visible_service_flag} and
-         * {@code selectable_service_flag} of {@code ciplus_service_descriptor} in CI Plus 1.3
-         * specification.
+         * {@code service_attribute_descriptor} in D-Book, the specification for UK-based TV
+         * products, {@code visible_service_flag} and {@code selectable_service_flag} of
+         * {@code ciplus_service_descriptor} in the CI Plus 1.3 specification.
          *
          * <p>The value should match one of the following:
          * {@link #BROADCAST_VISIBILITY_TYPE_VISIBLE},
@@ -2553,8 +2555,8 @@
          * by default.
          *
          * <p>Type: INTEGER
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
 
         /** @hide */
@@ -2571,8 +2573,8 @@
          * visible from users and selectable by users via normal service navigation mechanisms.
          *
          * @see #COLUMN_BROADCAST_VISIBILITY_TYPE
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0;
 
         /**
@@ -2581,18 +2583,18 @@
          * the logical channel number.
          *
          * @see #COLUMN_BROADCAST_VISIBILITY_TYPE
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1;
 
         /**
          * The broadcast visibility type for invisible services. Use this type when the service
-         * is invisible from users and unselectable by users via any of normal service navigation
-         * mechanisms.
+         * is invisible from users and not able to be selected by users via any of the normal
+         * service navigation mechanisms.
          *
          * @see #COLUMN_BROADCAST_VISIBILITY_TYPE
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2;
 
         private Channels() {}
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 78d7d76..2ebb19a 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -55,6 +55,27 @@
      */
     public static final int TYPE_SUBTITLE = 2;
 
+    /**
+     * The component tag identifies a component carried by a MPEG-2 TS.
+     *
+     * This corresponds to the component_tag in the component descriptor in the
+     * Elementary Stream loop of the stream in the Program Map Table
+     * (PMT) [EN 300 468], or undefined if the component is not carried in an
+     * MPEG-2 TS.
+     *
+     * @hide
+     */
+    public static final String EXTRA_BUNDLE_KEY_COMPONENT_TAG = "component_tag";
+
+    /**
+     * The MPEG Program ID (PID) of the component in the MPEG2-TS in
+     * which it is carried, or undefined if the component is not carried in an
+     * MPEG-2 TS.
+     *
+     * @hide
+     */
+    public static final String EXTRA_BUNDLE_KEY_PID = "pid";
+
     private final int mType;
     private final String mId;
     private final String mLanguage;
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
new file mode 100644
index 0000000..34d96b3
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.view.InputChannel;
+
+/**
+ * Interface a client of the ITvAdManager implements, to identify itself and receive
+ * information about changes to the state of each TV AD service.
+ * @hide
+ */
+oneway interface ITvAdClient {
+    void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
+    void onSessionReleased(int seq);
+    void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 92cc923..a747e49 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,10 +16,25 @@
 
 package android.media.tv.ad;
 
+import android.media.tv.ad.ITvAdClient;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.view.Surface;
+
 /**
  * Interface to the TV AD service.
  * @hide
  */
 interface ITvAdManager {
+    List<TvAdServiceInfo> getTvAdServiceList(int userId);
+    void createSession(
+            in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
+    void releaseSession(in IBinder sessionToken, int userId);
     void startAdService(in IBinder sessionToken, int userId);
+    void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+    void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
+            int userId);
+
+    void registerCallback(in ITvAdManagerCallback callback, int userId);
+    void unregisterCallback(in ITvAdManagerCallback callback, int userId);
 }
diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
similarity index 66%
copy from core/java/android/hardware/biometrics/PromptContentListItem.java
copy to media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
index fa3783d..f55f67e 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItem.java
+++ b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
@@ -14,16 +14,14 @@
  * limitations under the License.
  */
 
-package android.hardware.biometrics;
-
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
+package android.media.tv.ad;
 
 /**
- * A list item shown on {@link PromptVerticalListContentView}.
+ * Interface to receive callbacks from ITvAdManager regardless of sessions.
+ * @hide
  */
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public interface PromptContentListItem {
-}
-
+oneway interface ITvAdManagerCallback {
+    void onAdServiceAdded(in String serviceId);
+    void onAdServiceRemoved(in String serviceId);
+    void onAdServiceUpdated(in String serviceId);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdService.aidl b/media/java/android/media/tv/ad/ITvAdService.aidl
new file mode 100644
index 0000000..3bb0409
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdService.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.media.tv.ad;
+
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV AD component (implemented in a Service). It's used for
+ * TvAdManagerService to communicate with TvAdService.
+ * @hide
+ */
+oneway interface ITvAdService {
+    void registerCallback(in ITvAdServiceCallback callback);
+    void unregisterCallback(in ITvAdServiceCallback callback);
+    void createSession(in InputChannel channel, in ITvAdSessionCallback callback,
+            in String serviceId, in String type);
+    void sendAppLinkCommand(in Bundle command);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
similarity index 66%
copy from core/java/android/hardware/biometrics/PromptContentListItem.java
copy to media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
index fa3783d..a087181 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItem.java
+++ b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
@@ -14,16 +14,11 @@
  * limitations under the License.
  */
 
-package android.hardware.biometrics;
-
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
+package android.media.tv.ad;
 
 /**
- * A list item shown on {@link PromptVerticalListContentView}.
+ * Helper interface for ITvAdService to allow the TvAdService to notify the TvAdManagerService.
+ * @hide
  */
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public interface PromptContentListItem {
-}
-
+oneway interface ITvAdServiceCallback {
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index b834f1b9..751257c 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,10 +16,15 @@
 
 package android.media.tv.ad;
 
+import android.view.Surface;
+
 /**
- * Sub-interface of ITvAdService which is created per session and has its own context.
+ * Sub-interface of ITvAdService.aidl which is created per session and has its own context.
  * @hide
  */
 oneway interface ITvAdSession {
+    void release();
     void startAdService();
+    void setSurface(in Surface surface);
+    void dispatchSurfaceChanged(int format, int width, int height);
 }
diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
similarity index 63%
copy from core/java/android/hardware/biometrics/PromptContentListItem.java
copy to media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
index fa3783d..f21ef19 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItem.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package android.hardware.biometrics;
+package android.media.tv.ad;
 
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
+import android.media.tv.ad.ITvAdSession;
 
 /**
- * A list item shown on {@link PromptVerticalListContentView}.
+ * Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
+ * a related event.
+ * @hide
  */
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public interface PromptContentListItem {
-}
-
+oneway interface ITvAdSessionCallback {
+    void onSessionCreated(in ITvAdSession session);
+    void onLayoutSurface(int left, int top, int right, int bottom);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
new file mode 100644
index 0000000..4df2783
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -0,0 +1,152 @@
+/*
+ * 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.media.tv.ad;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Implements the internal ITvAdSession interface.
+ * @hide
+ */
+public class ITvAdSessionWrapper
+        extends ITvAdSession.Stub implements HandlerCaller.Callback {
+
+    private static final String TAG = "ITvAdSessionWrapper";
+
+    private static final int EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS = 1000;
+    private static final int EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS = 5 * 1000;
+    private static final int DO_RELEASE = 1;
+    private static final int DO_SET_SURFACE = 2;
+    private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+
+    private final HandlerCaller mCaller;
+    private TvAdService.Session mSessionImpl;
+    private InputChannel mChannel;
+    private TvAdEventReceiver mReceiver;
+
+    public ITvAdSessionWrapper(
+            Context context, TvAdService.Session mSessionImpl, InputChannel channel) {
+        this.mSessionImpl = mSessionImpl;
+        mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+        mChannel = channel;
+        if (channel != null) {
+            mReceiver = new TvAdEventReceiver(channel, context.getMainLooper());
+        }
+    }
+
+    @Override
+    public void release() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+    }
+
+
+    @Override
+    public void executeMessage(Message msg) {
+        if (mSessionImpl == null) {
+            return;
+        }
+
+        long startTime = System.nanoTime();
+        switch (msg.what) {
+            case DO_RELEASE: {
+                mSessionImpl.release();
+                mSessionImpl = null;
+                if (mReceiver != null) {
+                    mReceiver.dispose();
+                    mReceiver = null;
+                }
+                if (mChannel != null) {
+                    mChannel.dispose();
+                    mChannel = null;
+                }
+                break;
+            }
+            case DO_SET_SURFACE: {
+                mSessionImpl.setSurface((Surface) msg.obj);
+                break;
+            }
+            case DO_DISPATCH_SURFACE_CHANGED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.dispatchSurfaceChanged(
+                        (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
+                args.recycle();
+                break;
+            }
+            default: {
+                Log.w(TAG, "Unhandled message code: " + msg.what);
+                break;
+            }
+        }
+        long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
+        if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
+            Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
+                    + durationMs + "ms)");
+            if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
+                // TODO: handle timeout
+            }
+        }
+
+    }
+
+    @Override
+    public void startAdService() throws RemoteException {
+
+    }
+
+    @Override
+    public void setSurface(Surface surface) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+    }
+
+    @Override
+    public void dispatchSurfaceChanged(int format, int width, int height) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
+    }
+
+    private final class TvAdEventReceiver extends InputEventReceiver {
+        TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            if (mSessionImpl == null) {
+                // The session has been finished.
+                finishInputEvent(event, false);
+                return;
+            }
+
+            int handled = mSessionImpl.dispatchInputEvent(event, this);
+            if (handled != TvAdManager.Session.DISPATCH_IN_PROGRESS) {
+                finishInputEvent(
+                        event, handled == TvAdManager.Session.DISPATCH_HANDLED);
+            }
+        }
+    }
+}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 2b52c4b..9c75051 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -17,12 +17,30 @@
 package android.media.tv.ad;
 
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.media.tv.TvInputManager;
 import android.media.tv.flags.Flags;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pools;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.Surface;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Central system API to the overall client-side TV AD architecture, which arbitrates interaction
@@ -37,10 +55,163 @@
     private final ITvAdManager mService;
     private final int mUserId;
 
+    // A mapping from the sequence number of a session to its SessionCallbackRecord.
+    private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
+            new SparseArray<>();
+
+    // @GuardedBy("mLock")
+    private final List<TvAdServiceCallbackRecord> mCallbackRecords = new ArrayList<>();
+
+    // A sequence number for the next session to be created. Should be protected by a lock
+    // {@code mSessionCallbackRecordMap}.
+    private int mNextSeq;
+
+    private final Object mLock = new Object();
+    private final ITvAdClient mClient;
+
     /** @hide */
     public TvAdManager(ITvAdManager service, int userId) {
         mService = service;
         mUserId = userId;
+        mClient = new ITvAdClient.Stub() {
+            @Override
+            public void onSessionCreated(String serviceId, IBinder token, InputChannel channel,
+                    int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for " + token);
+                        return;
+                    }
+                    Session session = null;
+                    if (token != null) {
+                        session = new Session(token, channel, mService, mUserId, seq,
+                                mSessionCallbackRecordMap);
+                    } else {
+                        mSessionCallbackRecordMap.delete(seq);
+                    }
+                    record.postSessionCreated(session);
+                }
+            }
+
+            @Override
+            public void onSessionReleased(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    mSessionCallbackRecordMap.delete(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq:" + seq);
+                        return;
+                    }
+                    record.mSession.releaseInternal();
+                    record.postSessionReleased();
+                }
+            }
+
+            @Override
+            public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postLayoutSurface(left, top, right, bottom);
+                }
+            }
+
+        };
+
+        ITvAdManagerCallback managerCallback =
+                new ITvAdManagerCallback.Stub() {
+                    @Override
+                    public void onAdServiceAdded(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceAdded(serviceId);
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onAdServiceRemoved(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceRemoved(serviceId);
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onAdServiceUpdated(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceUpdated(serviceId);
+                            }
+                        }
+                    }
+                };
+        try {
+            if (mService != null) {
+                mService.registerCallback(managerCallback, mUserId);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the complete list of TV AD service on the system.
+     *
+     * @return List of {@link TvAdServiceInfo} for each TV AD service that describes its meta
+     * information.
+     * @hide
+     */
+    @NonNull
+    public List<TvAdServiceInfo> getTvAdServiceList() {
+        try {
+            return mService.getTvAdServiceList(mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a {@link Session} for a given TV AD service.
+     *
+     * <p>The number of sessions that can be created at the same time is limited by the capability
+     * of the given AD service.
+     *
+     * @param serviceId The ID of the AD service.
+     * @param callback A callback used to receive the created session.
+     * @param handler A {@link Handler} that the session creation will be delivered to.
+     * @hide
+     */
+    public void createSession(
+            @NonNull String serviceId,
+            @NonNull String type,
+            @NonNull final TvAdManager.SessionCallback callback,
+            @NonNull Handler handler) {
+        createSessionInternal(serviceId, type, callback, handler);
+    }
+
+    private void createSessionInternal(String serviceId, String type,
+            TvAdManager.SessionCallback callback, Handler handler) {
+        Preconditions.checkNotNull(serviceId);
+        Preconditions.checkNotNull(type);
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(handler);
+        TvAdManager.SessionCallbackRecord
+                record = new TvAdManager.SessionCallbackRecord(callback, handler);
+        synchronized (mSessionCallbackRecordMap) {
+            int seq = mNextSeq++;
+            mSessionCallbackRecordMap.put(seq, record);
+            try {
+                mService.createSession(mClient, serviceId, type, seq, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -48,14 +219,121 @@
      * @hide
      */
     public static final class Session {
-        private final IBinder mToken;
+        static final int DISPATCH_IN_PROGRESS = -1;
+        static final int DISPATCH_NOT_HANDLED = 0;
+        static final int DISPATCH_HANDLED = 1;
+
+        private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
         private final ITvAdManager mService;
         private final int mUserId;
+        private final int mSeq;
+        private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
 
-        private Session(IBinder token, ITvAdManager service, int userId) {
+        // For scheduling input event handling on the main thread. This also serves as a lock to
+        // protect pending input events and the input channel.
+        private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+        private TvInputManager.Session mInputSession;
+        private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
+        private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+        private TvInputEventSender mSender;
+        private InputChannel mInputChannel;
+        private IBinder mToken;
+
+        private Session(IBinder token, InputChannel channel, ITvAdManager service, int userId,
+                int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
             mToken = token;
+            mInputChannel = channel;
             mService = service;
             mUserId = userId;
+            mSeq = seq;
+            mSessionCallbackRecordMap = sessionCallbackRecordMap;
+        }
+
+        /**
+         * Releases this session.
+         */
+        public void release() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.releaseSession(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            releaseInternal();
+        }
+
+        /**
+         * Sets the {@link android.view.Surface} for this session.
+         *
+         * @param surface A {@link android.view.Surface} used to render AD.
+         */
+        public void setSurface(Surface surface) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            // surface can be null.
+            try {
+                mService.setSurface(mToken, surface, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies of any structural changes (format or size) of the surface passed in
+         * {@link #setSurface}.
+         *
+         * @param format The new PixelFormat of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        public void dispatchSurfaceChanged(int format, int width, int height) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        private void flushPendingEventsLocked() {
+            mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+            final int count = mPendingEvents.size();
+            for (int i = 0; i < count; i++) {
+                int seq = mPendingEvents.keyAt(i);
+                Message msg = mHandler.obtainMessage(
+                        InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
+        private void releaseInternal() {
+            mToken = null;
+            synchronized (mHandler) {
+                if (mInputChannel != null) {
+                    if (mSender != null) {
+                        flushPendingEventsLocked();
+                        mSender.dispose();
+                        mSender = null;
+                    }
+                    mInputChannel.dispose();
+                    mInputChannel = null;
+                }
+            }
+            synchronized (mSessionCallbackRecordMap) {
+                mSessionCallbackRecordMap.delete(mSeq);
+            }
         }
 
         void startAdService() {
@@ -69,5 +347,324 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        private final class InputEventHandler extends Handler {
+            public static final int MSG_SEND_INPUT_EVENT = 1;
+            public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+            public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+            InputEventHandler(Looper looper) {
+                super(looper, null, true);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_SEND_INPUT_EVENT: {
+                        sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+                        return;
+                    }
+                    case MSG_TIMEOUT_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, true);
+                        return;
+                    }
+                    case MSG_FLUSH_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, false);
+                        return;
+                    }
+                }
+            }
+        }
+
+        // Assumes the event has already been removed from the queue.
+        void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+            p.mHandled = handled;
+            if (p.mEventHandler.getLooper().isCurrentThread()) {
+                // Already running on the callback handler thread so we can send the callback
+                // immediately.
+                p.run();
+            } else {
+                // Post the event to the callback handler thread.
+                // In this case, the callback will be responsible for recycling the event.
+                Message msg = Message.obtain(p.mEventHandler, p);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
+        // Must be called on the main looper
+        private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+            synchronized (mHandler) {
+                int result = sendInputEventOnMainLooperLocked(p);
+                if (result == DISPATCH_IN_PROGRESS) {
+                    return;
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, false);
+        }
+
+        private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+            if (mInputChannel != null) {
+                if (mSender == null) {
+                    mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
+                }
+
+                final InputEvent event = p.mEvent;
+                final int seq = event.getSequenceNumber();
+                if (mSender.sendInputEvent(seq, event)) {
+                    mPendingEvents.put(seq, p);
+                    Message msg = mHandler.obtainMessage(
+                            InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                    msg.setAsynchronous(true);
+                    mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+                    return DISPATCH_IN_PROGRESS;
+                }
+
+                Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+                        + event);
+            }
+            return DISPATCH_NOT_HANDLED;
+        }
+
+        void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+            final PendingEvent p;
+            synchronized (mHandler) {
+                int index = mPendingEvents.indexOfKey(seq);
+                if (index < 0) {
+                    return; // spurious, event already finished or timed out
+                }
+
+                p = mPendingEvents.valueAt(index);
+                mPendingEvents.removeAt(index);
+
+                if (timeout) {
+                    Log.w(TAG, "Timeout waiting for session to handle input event after "
+                            + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+                } else {
+                    mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, handled);
+        }
+
+        private void recyclePendingEventLocked(PendingEvent p) {
+            p.recycle();
+            mPendingEventPool.release(p);
+        }
+
+        /**
+         * Callback that is invoked when an input event that was dispatched to this session has been
+         * finished.
+         *
+         * @hide
+         */
+        public interface FinishedInputEventCallback {
+            /**
+             * Called when the dispatched input event is finished.
+             *
+             * @param token A token passed to {@link #dispatchInputEvent}.
+             * @param handled {@code true} if the dispatched input event was handled properly.
+             *            {@code false} otherwise.
+             */
+            void onFinishedInputEvent(Object token, boolean handled);
+        }
+
+        private final class TvInputEventSender extends InputEventSender {
+            TvInputEventSender(InputChannel inputChannel, Looper looper) {
+                super(inputChannel, looper);
+            }
+
+            @Override
+            public void onInputEventFinished(int seq, boolean handled) {
+                finishedInputEvent(seq, handled, false);
+            }
+        }
+
+        private final class PendingEvent implements Runnable {
+            public InputEvent mEvent;
+            public Object mEventToken;
+            public Session.FinishedInputEventCallback mCallback;
+            public Handler mEventHandler;
+            public boolean mHandled;
+
+            public void recycle() {
+                mEvent = null;
+                mEventToken = null;
+                mCallback = null;
+                mEventHandler = null;
+                mHandled = false;
+            }
+
+            @Override
+            public void run() {
+                mCallback.onFinishedInputEvent(mEventToken, mHandled);
+
+                synchronized (mEventHandler) {
+                    recyclePendingEventLocked(this);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Interface used to receive the created session.
+     * @hide
+     */
+    public abstract static class SessionCallback {
+        /**
+         * This is called after {@link TvAdManager#createSession} has been processed.
+         *
+         * @param session A {@link TvAdManager.Session} instance created. This can be
+         *                {@code null} if the creation request failed.
+         */
+        public void onSessionCreated(@Nullable Session session) {
+        }
+
+        /**
+         * This is called when {@link TvAdManager.Session} is released.
+         * This typically happens when the process hosting the session has crashed or been killed.
+         *
+         * @param session the {@link TvAdManager.Session} instance released.
+         */
+        public void onSessionReleased(@NonNull Session session) {
+        }
+
+        /**
+         * This is called when {@link TvAdService.Session#layoutSurface} is called to
+         * change the layout of surface.
+         *
+         * @param session A {@link TvAdManager.Session} associated with this callback.
+         * @param left Left position.
+         * @param top Top position.
+         * @param right Right position.
+         * @param bottom Bottom position.
+         */
+        public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
+        }
+
+    }
+
+    /**
+     * Callback used to monitor status of the TV AD service.
+     * @hide
+     */
+    public abstract static class TvAdServiceCallback {
+        /**
+         * This is called when a TV AD service is added to the system.
+         *
+         * <p>Normally it happens when the user installs a new TV AD service package that implements
+         * {@link TvAdService} interface.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceAdded(@NonNull String serviceId) {
+        }
+
+        /**
+         * This is called when a TV AD service is removed from the system.
+         *
+         * <p>Normally it happens when the user uninstalls the previously installed TV AD service
+         * package.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceRemoved(@NonNull String serviceId) {
+        }
+
+        /**
+         * This is called when a TV AD service is updated on the system.
+         *
+         * <p>Normally it happens when a previously installed TV AD service package is re-installed
+         * or a newer version of the package exists becomes available/unavailable.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceUpdated(@NonNull String serviceId) {
+        }
+
+    }
+
+    private static final class SessionCallbackRecord {
+        private final SessionCallback mSessionCallback;
+        private final Handler mHandler;
+        private Session mSession;
+
+        SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
+            mSessionCallback = sessionCallback;
+            mHandler = handler;
+        }
+
+        void postSessionCreated(final Session session) {
+            mSession = session;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionCreated(session);
+                }
+            });
+        }
+
+        void postSessionReleased() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionReleased(mSession);
+                }
+            });
+        }
+
+        void postLayoutSurface(final int left, final int top, final int right,
+                final int bottom) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
+                }
+            });
+        }
+    }
+
+    private static final class TvAdServiceCallbackRecord {
+        private final TvAdServiceCallback mCallback;
+        private final Executor mExecutor;
+
+        TvAdServiceCallbackRecord(TvAdServiceCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public TvAdServiceCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postAdServiceAdded(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceAdded(serviceId);
+                }
+            });
+        }
+
+        public void postAdServiceRemoved(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceRemoved(serviceId);
+                }
+            });
+        }
+
+        public void postAdServiceUpdated(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceUpdated(serviceId);
+                }
+            });
+        }
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6897a78..6995703 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -16,8 +16,37 @@
 
 package android.media.tv.ad;
 
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * The TvAdService class represents a TV client-side advertisement service.
@@ -36,9 +65,123 @@
     public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
 
     /**
+     * This is the interface name that a service implementing a TV AD service should
+     * say that it supports -- that is, this is the action it uses for its intent filter. To be
+     * supported, the service must also require the
+     * android.Manifest.permission#BIND_TV_AD_SERVICE permission so that other
+     * applications cannot abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
+
+    private final Handler mServiceHandler = new ServiceHandler();
+    private final RemoteCallbackList<ITvAdServiceCallback> mCallbacks = new RemoteCallbackList<>();
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
+        ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
+            @Override
+            public void registerCallback(ITvAdServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.register(cb);
+                }
+            }
+
+            @Override
+            public void unregisterCallback(ITvAdServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.unregister(cb);
+                }
+            }
+
+            @Override
+            public void createSession(InputChannel channel, ITvAdSessionCallback cb,
+                    String serviceId, String type) {
+                if (cb == null) {
+                    return;
+                }
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = channel;
+                args.arg2 = cb;
+                args.arg3 = serviceId;
+                args.arg4 = type;
+                mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
+                        .sendToTarget();
+            }
+
+            @Override
+            public void sendAppLinkCommand(Bundle command) {
+                onAppLinkCommand(command);
+            }
+        };
+        return tvAdServiceBinder;
+    }
+
+    /**
+     * Called when app link command is received.
+     */
+    public void onAppLinkCommand(@NonNull Bundle command) {
+    }
+
+
+    /**
+     * Returns a concrete implementation of {@link Session}.
+     *
+     * <p>May return {@code null} if this TV AD service fails to create a session for some
+     * reason.
+     *
+     * @param serviceId The ID of the TV AD associated with the session.
+     * @param type The type of the TV AD associated with the session.
+     */
+    @Nullable
+    public abstract Session onCreateSession(@NonNull String serviceId, @NonNull String type);
+
+    /**
      * Base class for derived classes to implement to provide a TV AD session.
      */
     public abstract static class Session implements KeyEvent.Callback {
+        private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+
+        private final Object mLock = new Object();
+        // @GuardedBy("mLock")
+        private ITvAdSessionCallback mSessionCallback;
+        // @GuardedBy("mLock")
+        private final List<Runnable> mPendingActions = new ArrayList<>();
+        private final Context mContext;
+        final Handler mHandler;
+        private final WindowManager mWindowManager;
+        private Surface mSurface;
+
+
+        /**
+         * Creates a new Session.
+         *
+         * @param context The context of the application
+         */
+        public Session(@NonNull Context context) {
+            mContext = context;
+            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+            mHandler = new Handler(context.getMainLooper());
+        }
+
+        /**
+         * Releases TvAdService session.
+         */
+        public abstract void onRelease();
+
+        void release() {
+            onRelease();
+            if (mSurface != null) {
+                mSurface.release();
+                mSurface = null;
+            }
+            synchronized (mLock) {
+                mSessionCallback = null;
+                mPendingActions.clear();
+            }
+        }
+
         /**
          * Starts TvAdService session.
          */
@@ -48,21 +191,264 @@
         void startAdService() {
             onStartAdService();
         }
-    }
 
-    /**
-     * Implements the internal ITvAdService interface.
-     */
-    public static class ITvAdSessionWrapper extends ITvAdSession.Stub {
-        private final Session mSessionImpl;
-
-        public ITvAdSessionWrapper(Session mSessionImpl) {
-            this.mSessionImpl = mSessionImpl;
+        @Override
+        public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+            return false;
         }
 
         @Override
-        public void startAdService() {
-            mSessionImpl.startAdService();
+        public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
+            return false;
         }
+
+        @Override
+        public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle touch screen motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTouchEvent
+         */
+        public boolean onTouchEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle trackball events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTrackballEvent
+         */
+        public boolean onTrackballEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle generic motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onGenericMotionEvent
+         */
+        public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
+         * is relative to the overlay view that sits on top of this surface.
+         *
+         * @param left Left position in pixels, relative to the overlay view.
+         * @param top Top position in pixels, relative to the overlay view.
+         * @param right Right position in pixels, relative to the overlay view.
+         * @param bottom Bottom position in pixels, relative to the overlay view.
+         */
+        @CallSuper
+        public void layoutSurface(final int left, final int top, final int right,
+                final int bottom) {
+            if (left > right || top > bottom) {
+                throw new IllegalArgumentException("Invalid parameter");
+            }
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
+                                    + ", r=" + right + ", b=" + bottom + ",)");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onLayoutSurface(left, top, right, bottom);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in layoutSurface", e);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Called when the application sets the surface.
+         *
+         * <p>The TV AD service should render AD UI onto the given surface. When called with
+         * {@code null}, the AD service should immediately free any references to the currently set
+         * surface and stop using it.
+         *
+         * @param surface The surface to be used for AD UI rendering. Can be {@code null}.
+         * @return {@code true} if the surface was set successfully, {@code false} otherwise.
+         */
+        public abstract boolean onSetSurface(@Nullable Surface surface);
+
+        /**
+         * Called after any structural changes (format or size) have been made to the surface passed
+         * in {@link #onSetSurface}. This method is always called at least once, after
+         * {@link #onSetSurface} is called with non-null surface.
+         *
+         * @param format The new {@link PixelFormat} of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
+        }
+
+        /**
+         * Takes care of dispatching incoming input events and tells whether the event was handled.
+         */
+        int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+            if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+            if (event instanceof KeyEvent) {
+                KeyEvent keyEvent = (KeyEvent) event;
+                if (keyEvent.dispatch(this, mDispatcherState, this)) {
+                    return TvAdManager.Session.DISPATCH_HANDLED;
+                }
+
+                // TODO: special handlings of navigation keys and media keys
+            } else if (event instanceof MotionEvent) {
+                MotionEvent motionEvent = (MotionEvent) event;
+                final int source = motionEvent.getSource();
+                if (motionEvent.isTouchEvent()) {
+                    if (onTouchEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                    if (onTrackballEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                } else {
+                    if (onGenericMotionEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                }
+            }
+            // TODO: handle overlay view
+            return TvAdManager.Session.DISPATCH_NOT_HANDLED;
+        }
+
+
+        private void initialize(ITvAdSessionCallback callback) {
+            synchronized (mLock) {
+                mSessionCallback = callback;
+                for (Runnable runnable : mPendingActions) {
+                    runnable.run();
+                }
+                mPendingActions.clear();
+            }
+        }
+
+        /**
+         * Calls {@link #onSetSurface}.
+         */
+        void setSurface(Surface surface) {
+            onSetSurface(surface);
+            if (mSurface != null) {
+                mSurface.release();
+            }
+            mSurface = surface;
+            // TODO: Handle failure.
+        }
+
+        /**
+         * Calls {@link #onSurfaceChanged}.
+         */
+        void dispatchSurfaceChanged(int format, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+                        + ", height=" + height + ")");
+            }
+            onSurfaceChanged(format, width, height);
+        }
+
+        private void executeOrPostRunnableOnMainThread(Runnable action) {
+            synchronized (mLock) {
+                if (mSessionCallback == null) {
+                    // The session is not initialized yet.
+                    mPendingActions.add(action);
+                } else {
+                    if (mHandler.getLooper().isCurrentThread()) {
+                        action.run();
+                    } else {
+                        // Posts the runnable if this is not called from the main thread
+                        mHandler.post(action);
+                    }
+                }
+            }
+        }
+    }
+
+
+    @SuppressLint("HandlerLeak")
+    private final class ServiceHandler extends Handler {
+        private static final int DO_CREATE_SESSION = 1;
+        private static final int DO_NOTIFY_SESSION_CREATED = 2;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DO_CREATE_SESSION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    InputChannel channel = (InputChannel) args.arg1;
+                    ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg2;
+                    String serviceId = (String) args.arg3;
+                    String type = (String) args.arg4;
+                    args.recycle();
+                    TvAdService.Session sessionImpl = onCreateSession(serviceId, type);
+                    if (sessionImpl == null) {
+                        try {
+                            // Failed to create a session.
+                            cb.onSessionCreated(null);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "error in onSessionCreated", e);
+                        }
+                        return;
+                    }
+                    ITvAdSession stub =
+                            new ITvAdSessionWrapper(TvAdService.this, sessionImpl, channel);
+
+                    SomeArgs someArgs = SomeArgs.obtain();
+                    someArgs.arg1 = sessionImpl;
+                    someArgs.arg2 = stub;
+                    someArgs.arg3 = cb;
+                    mServiceHandler.obtainMessage(
+                            DO_NOTIFY_SESSION_CREATED, someArgs).sendToTarget();
+                    return;
+                }
+                case DO_NOTIFY_SESSION_CREATED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Session sessionImpl = (Session) args.arg1;
+                    ITvAdSession stub = (ITvAdSession) args.arg2;
+                    ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg3;
+                    try {
+                        cb.onSessionCreated(stub);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in onSessionCreated", e);
+                    }
+                    if (sessionImpl != null) {
+                        sessionImpl.initialize(cb);
+                    }
+                    args.recycle();
+                    return;
+                }
+                default: {
+                    Log.w(TAG, "Unhandled message code: " + msg.what);
+                    return;
+                }
+            }
+        }
+
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
index ed04f1f..45dc89d 100644
--- a/media/java/android/media/tv/ad/TvAdServiceInfo.java
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -24,6 +24,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -63,8 +64,7 @@
         if (context == null) {
             throw new IllegalArgumentException("context cannot be null.");
         }
-        // TODO: use a constant
-        Intent intent = new Intent("android.media.tv.ad.TvAdService").setComponent(component);
+        Intent intent = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
         ResolveInfo resolveInfo = context.getPackageManager().resolveService(
                 intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
         if (resolveInfo == null) {
@@ -80,6 +80,7 @@
 
         mService = resolveInfo;
         mId = id;
+        mTypes.addAll(types);
     }
 
     private TvAdServiceInfo(ResolveInfo service, String id, List<String> types) {
@@ -147,9 +148,8 @@
             ResolveInfo resolveInfo, Context context, List<String> types) {
         ServiceInfo serviceInfo = resolveInfo.serviceInfo;
         PackageManager pm = context.getPackageManager();
-        // TODO: use constant for the metadata
         try (XmlResourceParser parser =
-                     serviceInfo.loadXmlMetaData(pm, "android.media.tv.ad.service")) {
+                     serviceInfo.loadXmlMetaData(pm, TvAdService.SERVICE_META_DATA)) {
             if (parser == null) {
                 throw new IllegalStateException(
                         "No " + "android.media.tv.ad.service"
@@ -171,7 +171,15 @@
                         + XML_START_TAG_NAME + " tag for " + serviceInfo.name);
             }
 
-            // TODO: parse attributes
+            TypedArray sa = resources.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.TvAdService);
+            CharSequence[] textArr = sa.getTextArray(
+                    com.android.internal.R.styleable.TvAdService_adServiceTypes);
+            for (CharSequence cs : textArr) {
+                types.add(cs.toString().toLowerCase());
+            }
+
+            sa.recycle();
         } catch (IOException | XmlPullParserException e) {
             throw new IllegalStateException(
                     "Failed reading meta-data for " + serviceInfo.packageName, e);
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 1a3771a..5e67fe9 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -16,8 +16,20 @@
 
 package android.media.tv.ad;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Xml;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
 import android.view.ViewGroup;
 
 /**
@@ -28,18 +40,166 @@
     private static final String TAG = "TvAdView";
     private static final boolean DEBUG = false;
 
-    // TODO: create session
-    private TvAdManager.Session mSession;
+    private final TvAdManager mTvAdManager;
 
-    public TvAdView(Context context) {
-        super(context, /* attrs = */null, /* defStyleAttr = */0);
+    private final Handler mHandler = new Handler();
+    private TvAdManager.Session mSession;
+    private MySessionCallback mSessionCallback;
+
+    private final AttributeSet mAttrs;
+    private final int mDefStyleAttr;
+    private final XmlResourceParser mParser;
+
+    private SurfaceView mSurfaceView;
+    private Surface mSurface;
+
+    private boolean mSurfaceChanged;
+    private int mSurfaceFormat;
+    private int mSurfaceWidth;
+    private int mSurfaceHeight;
+
+    private boolean mUseRequestedSurfaceLayout;
+    private int mSurfaceViewLeft;
+    private int mSurfaceViewRight;
+    private int mSurfaceViewTop;
+    private int mSurfaceViewBottom;
+
+
+
+    private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format
+                        + ", width=" + width + ", height=" + height + ")");
+            }
+            mSurfaceFormat = format;
+            mSurfaceWidth = width;
+            mSurfaceHeight = height;
+            mSurfaceChanged = true;
+            dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            mSurface = holder.getSurface();
+            setSessionSurface(mSurface);
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            mSurface = null;
+            mSurfaceChanged = false;
+            setSessionSurface(null);
+        }
+    };
+
+
+    public TvAdView(@NonNull Context context) {
+        this(context, null, 0);
+    }
+
+    public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+        if (sourceResId != Resources.ID_NULL) {
+            Log.d(TAG, "Build local AttributeSet");
+            mParser  = context.getResources().getXml(sourceResId);
+            mAttrs = Xml.asAttributeSet(mParser);
+        } else {
+            Log.d(TAG, "Use passed in AttributeSet");
+            mParser = null;
+            mAttrs = attrs;
+        }
+        mDefStyleAttr = defStyleAttr;
+        resetSurfaceView();
+        mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
     }
 
     @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (DEBUG) {
-            Log.d(TAG,
-                    "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+            Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
+                    + ", bottom=" + bottom + ",)");
+        }
+        if (mUseRequestedSurfaceLayout) {
+            mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
+                    mSurfaceViewBottom);
+        } else {
+            mSurfaceView.layout(0, 0, right - left, bottom - top);
+        }
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
+        int width = mSurfaceView.getMeasuredWidth();
+        int height = mSurfaceView.getMeasuredHeight();
+        int childState = mSurfaceView.getMeasuredState();
+        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+                resolveSizeAndState(height, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    public void onVisibilityChanged(@NonNull View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        mSurfaceView.setVisibility(visibility);
+    }
+
+    private void resetSurfaceView() {
+        if (mSurfaceView != null) {
+            mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
+            removeView(mSurfaceView);
+        }
+        mSurface = null;
+        mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+            @Override
+            protected void updateSurface() {
+                super.updateSurface();
+            }};
+        // The surface view's content should be treated as secure all the time.
+        mSurfaceView.setSecure(true);
+        mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+        mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+
+        mSurfaceView.setZOrderOnTop(false);
+        mSurfaceView.setZOrderMediaOverlay(true);
+
+        addView(mSurfaceView);
+    }
+
+    private void setSessionSurface(Surface surface) {
+        if (mSession == null) {
+            return;
+        }
+        mSession.setSurface(surface);
+    }
+
+    private void dispatchSurfaceChanged(int format, int width, int height) {
+        if (mSession == null) {
+            return;
+        }
+        //mSession.dispatchSurfaceChanged(format, width, height);
+    }
+
+    /**
+     * Prepares the AD service of corresponding {@link TvAdService}.
+     *
+     * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
+     */
+    public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
+        if (DEBUG) {
+            Log.d(TAG, "prepareAdService");
+        }
+        mSessionCallback = new TvAdView.MySessionCallback(serviceId);
+        if (mTvAdManager != null) {
+            mTvAdManager.createSession(serviceId, type, mSessionCallback, mHandler);
         }
     }
 
@@ -54,4 +214,75 @@
             mSession.startAdService();
         }
     }
+
+    private class MySessionCallback extends TvAdManager.SessionCallback {
+        final String mServiceId;
+
+        MySessionCallback(String serviceId) {
+            mServiceId = serviceId;
+        }
+
+        @Override
+        public void onSessionCreated(TvAdManager.Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onSessionCreated()");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onSessionCreated - session already created");
+                // This callback is obsolete.
+                if (session != null) {
+                    session.release();
+                }
+                return;
+            }
+            mSession = session;
+            if (session != null) {
+                // mSurface may not be ready yet as soon as starting an application.
+                // In the case, we don't send Session.setSurface(null) unnecessarily.
+                // setSessionSurface will be called in surfaceCreated.
+                if (mSurface != null) {
+                    setSessionSurface(mSurface);
+                    if (mSurfaceChanged) {
+                        dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+                    }
+                }
+            } else {
+                // Failed to create
+                // Todo: forward error to Tv App
+                mSessionCallback = null;
+            }
+        }
+
+        @Override
+        public void onSessionReleased(TvAdManager.Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onSessionReleased()");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onSessionReleased - session not created");
+                return;
+            }
+            mSessionCallback = null;
+            mSession = null;
+        }
+
+        @Override
+        public void onLayoutSurface(
+                TvAdManager.Session session, int left, int top, int right, int bottom) {
+            if (DEBUG) {
+                Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
+                        + right + ", bottom=" + bottom + ",)");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onLayoutSurface - session not created");
+                return;
+            }
+            mSurfaceViewLeft = left;
+            mSurfaceViewTop = top;
+            mSurfaceViewRight = right;
+            mSurfaceViewBottom = bottom;
+            mUseRequestedSurfaceLayout = true;
+            requestLayout();
+        }
+    }
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 7739184..e3dba03 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -48,6 +48,7 @@
     void onRequestCurrentChannelLcn(int seq);
     void onRequestStreamVolume(int seq);
     void onRequestTrackInfoList(int seq);
+    void onRequestSelectedTrackInfo(int seq);
     void onRequestCurrentTvInputId(int seq);
     void onRequestTimeShiftMode(int seq);
     void onRequestAvailableSpeeds(int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 41cbe4a..4316d05 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -102,6 +102,8 @@
             int UserId);
     void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId);
     void notifyAdBufferConsumed(in IBinder sessionToken, in AdBuffer buffer, int userId);
+    void sendSelectedTrackInfo(in IBinder sessionToken, in List<TvTrackInfo> tracks,
+            int userId);
 
     void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 052bc3d..ba7cf13 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -78,6 +78,7 @@
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
     void notifyAdResponse(in AdResponse response);
     void notifyAdBufferConsumed(in AdBuffer buffer);
+    void sendSelectedTrackInfo(in List<TvTrackInfo> tracks);
 
     void createMediaView(in IBinder windowToken, in Rect frame);
     void relayoutMediaView(in Rect frame);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 9e43e79..416b8f1 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -50,6 +50,7 @@
     void onRequestCurrentTvInputId();
     void onRequestTimeShiftMode();
     void onRequestAvailableSpeeds();
+    void onRequestSelectedTrackInfo();
     void onRequestStartRecording(in String requestId, in Uri programUri);
     void onRequestStopRecording(in String recordingId);
     void onRequestScheduleRecording(in String requestId, in String inputId, in Uri channelUri,
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 253ade8..518b08a 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -102,6 +102,7 @@
     private static final int DO_NOTIFY_RECORDING_SCHEDULED = 45;
     private static final int DO_SEND_TIME_SHIFT_MODE = 46;
     private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
+    private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -247,6 +248,10 @@
                 args.recycle();
                 break;
             }
+            case DO_SEND_SELECTED_TRACK_INFO: {
+                mSessionImpl.sendSelectedTrackInfo((List<TvTrackInfo>) msg.obj);
+                break;
+            }
             case DO_NOTIFY_VIDEO_AVAILABLE: {
                 mSessionImpl.notifyVideoAvailable();
                 break;
@@ -526,6 +531,12 @@
     }
 
     @Override
+    public void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_SELECTED_TRACK_INFO, tracks));
+    }
+
+    @Override
     public void notifyTracksChanged(List<TvTrackInfo> tracks) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_TRACKS_CHANGED, tracks));
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7cce84a..bf4379f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -33,7 +33,6 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.TvInteractiveAppService.Session;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -506,6 +505,18 @@
             }
 
             @Override
+            public void onRequestSelectedTrackInfo(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestSelectedTrackInfo();
+                }
+            }
+
+            @Override
             public void onRequestCurrentTvInputId(int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1209,6 +1220,18 @@
             }
         }
 
+        void sendSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendSelectedTrackInfo(mToken, tracks, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendCurrentTvInputId(@Nullable String inputId) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -2108,6 +2131,15 @@
             });
         }
 
+        void postRequestSelectedTrackInfo() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestSelectedTrackInfo(mSession);
+                }
+            });
+        }
+
         void postRequestCurrentTvInputId() {
             mHandler.post(new Runnable() {
                 @Override
@@ -2378,6 +2410,15 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
+         */
+        public void onRequestSelectedTrackInfo(Session session) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is
          * called.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 2419404..5cc86ba 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -932,6 +932,16 @@
                 @NonNull Bundle data) {
         }
 
+        /**
+         * Called when the TV App sends the selected track info as a response to
+         * requestSelectedTrackInfo.
+         *
+         * @param tracks
+         * @hide
+         */
+        public void onSelectedTrackInfo(List<TvTrackInfo> tracks) {
+        }
+
         @Override
         public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
             return false;
@@ -1338,6 +1348,30 @@
         }
 
         /**
+         * Requests the currently selected {@link TvTrackInfo} from the TV App.
+         *
+         * <p> Normally, track info cannot be synchronized until the channel has
+         * been changed. This is used when the session of the TIAS is newly
+         * created and the normal synchronization has not happened yet.
+         * @hide
+         */
+        @CallSuper
+        public void requestSelectedTrackInfo() {
+            executeOrPostRunnableOnMainThread(() -> {
+                try {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestSelectedTrackInfo");
+                    }
+                    if (mSessionCallback != null) {
+                        mSessionCallback.onRequestSelectedTrackInfo();
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "error in requestSelectedTrackInfo", e);
+                }
+            });
+        }
+
+        /**
          * Requests starting of recording
          *
          * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
@@ -1781,6 +1815,13 @@
             onTvMessage(type, data);
         }
 
+        void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+            if (DEBUG) {
+                Log.d(TAG, "notifySelectedTrackInfo (tracks= " + tracks + ")");
+            }
+            onSelectedTrackInfo(tracks);
+        }
+
         /**
          * Calls {@link #onAdBufferConsumed}.
          */
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index cbaf5e4..40a12e4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -582,6 +582,20 @@
     }
 
     /**
+     * Sends the currently selected track info to the TV Interactive App.
+     *
+     * @hide
+     */
+    public void sendSelectedTrackInfo(@Nullable List<TvTrackInfo> tracks) {
+        if (DEBUG) {
+            Log.d(TAG, "sendSelectedTrackInfo");
+        }
+        if (mSession != null) {
+            mSession.sendSelectedTrackInfo(tracks);
+        }
+    }
+
+    /**
      * Sends current TV input ID to related TV interactive app.
      *
      * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
@@ -1197,6 +1211,16 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+         * called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
+         */
+        public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId()} is
          * called.
          *
@@ -1714,6 +1738,28 @@
         }
 
         @Override
+        public void onRequestSelectedTrackInfo(Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestSelectedTrackInfo");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestSelectedTrackInfo - session not created");
+                return;
+            }
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestSelectedTrackInfo(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentTvInputId(Session session) {
             if (DEBUG) {
                 Log.d(TAG, "onRequestCurrentTvInputId");
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 3fcb871..00b0e57 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -967,7 +967,7 @@
     ScopedLocalRef<jobject> filter(env);
     {
         android::Mutex::Autolock autoLock(mLock);
-        if (env->IsSameObject(filter.get(), nullptr)) {
+        if (env->IsSameObject(mFilterObj, nullptr)) {
             ALOGE("FilterClientCallbackImpl::onFilterStatus:"
                   "Filter object has been freed. Ignoring callback.");
             return;
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 87da299..74bec3e 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -69,6 +69,7 @@
     jarjar_rules: ":nfc-jarjar-rules",
     lint: {
         strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
     },
     apex_available: [
         "//apex_available:platform",
diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml
new file mode 100644
index 0000000..1dfdd29
--- /dev/null
+++ b/nfc/lint-baseline.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`"
+        errorLine1="        AidGroup aidGroup = new AidGroup(aids, category);"
+        errorLine2="                            ~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="377"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+        errorLine1="            return (group != null ? group.getAids() : null);"
+        errorLine2="                                          ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="537"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+        errorLine1="                return (group != null ? group.getAids() : null);"
+        errorLine2="                                              ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="547"
+            column="47"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+        errorLine1="            return (serviceInfo != null ? serviceInfo.getAids() : null);"
+        errorLine2="                                                      ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="714"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+        errorLine1="                return (serviceInfo != null ? serviceInfo.getAids() : null);"
+        errorLine2="                                                          ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="724"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+        errorLine1="                if (!serviceInfo.isOnHost()) {"
+        errorLine2="                                 ~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="755"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1="                    return serviceInfo.getOffHostSecureElement() == null ?"
+        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="756"
+            column="40"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1='                            "OffHost" : serviceInfo.getOffHostSecureElement();'
+        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="757"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+        errorLine1="                    if (!serviceInfo.isOnHost()) {"
+        errorLine2="                                     ~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="772"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1="                        return serviceInfo.getOffHostSecureElement() == null ?"
+        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="773"
+            column="44"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1='                                "Offhost" : serviceInfo.getOffHostSecureElement();'
+        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="774"
+            column="57"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+        errorLine1="            return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+        errorLine2="                                                      ~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="798"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+        errorLine1="                return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+        errorLine2="                                                          ~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="808"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="1032"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="1066"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="            resumed = activity.isResumed();"
+        errorLine2="                               ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java"
+            line="124"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java"
+            line="2457"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+            line="315"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+            line="351"
+            column="23"/>
+    </issue>
+
+</issues>
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b0abf92..2d442f4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1349,6 +1349,26 @@
             final int nameCount = names.size();
             HashMap<String, String> flagsToValues = new HashMap<>(names.size());
 
+            if (Flags.loadAconfigDefaults()) {
+                Map<String, Map<String, String>> allDefaults =
+                        settingsState.getAconfigDefaultValues();
+
+                if (allDefaults != null) {
+                    if (prefix != null) {
+                        String namespace = prefix.substring(0, prefix.length() - 1);
+
+                        Map<String, String> namespaceDefaults = allDefaults.get(namespace);
+                        if (namespaceDefaults != null) {
+                            flagsToValues.putAll(namespaceDefaults);
+                        }
+                    } else {
+                        for (Map<String, String> namespaceDefaults : allDefaults.values()) {
+                            flagsToValues.putAll(namespaceDefaults);
+                        }
+                    }
+                }
+            }
+
             for (int i = 0; i < nameCount; i++) {
                 String name = names.get(i);
                 Setting setting = settingsState.getSettingLocked(name);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 73c2e22..6f3c88f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -69,6 +69,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -236,6 +237,10 @@
     @GuardedBy("mLock")
     private int mNextHistoricalOpIdx;
 
+    @GuardedBy("mLock")
+    @Nullable
+    private Map<String, Map<String, String>> mNamespaceDefaults;
+
     public static final int SETTINGS_TYPE_GLOBAL = 0;
     public static final int SETTINGS_TYPE_SYSTEM = 1;
     public static final int SETTINGS_TYPE_SECURE = 2;
@@ -331,25 +336,21 @@
             readStateSyncLocked();
 
             if (Flags.loadAconfigDefaults()) {
-                // Only load aconfig defaults if this is the first boot, the XML
-                // file doesn't exist yet, or this device is on its first boot after
-                // an OTA.
-                boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey)
-                        && (!file.exists()
-                                || mContext.getPackageManager().isDeviceUpgrading());
-                if (shouldLoadAconfigValues) {
+                if (isConfigSettingsKey(mKey)) {
                     loadAconfigDefaultValuesLocked();
                 }
             }
+
         }
     }
 
     @GuardedBy("mLock")
     private void loadAconfigDefaultValuesLocked() {
+        mNamespaceDefaults = new HashMap<>();
+
         for (String fileName : sAconfigTextProtoFilesOnDevice) {
             try (FileInputStream inputStream = new FileInputStream(fileName)) {
-                byte[] contents = inputStream.readAllBytes();
-                loadAconfigDefaultValues(contents);
+                loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "failed to read protobuf", e);
             }
@@ -358,27 +359,21 @@
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    public void loadAconfigDefaultValues(byte[] fileContents) {
+    public static void loadAconfigDefaultValues(byte[] fileContents,
+            @NonNull Map<String, Map<String, String>> defaultMap) {
         try {
-            parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
-
-            if (parsedFlags == null) {
-                Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
-                return;
-            }
-
+            parsed_flags parsedFlags =
+                    parsed_flags.parseFrom(fileContents);
             for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
-                String flagName = flag.getNamespace() + "/"
-                        + flag.getPackage() + "." + flag.getName();
-                String value = flag.getState() == flag_state.ENABLED ? "true" : "false";
-
-                Setting existingSetting = getSettingLocked(flagName);
-                boolean isDefaultLoaded = existingSetting.getTag() != null
-                        && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG);
-                if (existingSetting.getValue() == null || isDefaultLoaded) {
-                    insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG,
-                            false, flag.getPackage());
+                if (!defaultMap.containsKey(flag.getNamespace())) {
+                    Map<String, String> defaults = new HashMap<>();
+                    defaultMap.put(flag.getNamespace(), defaults);
                 }
+                String flagName = flag.getNamespace()
+                        + "/" + flag.getPackage() + "." + flag.getName();
+                String flagValue = flag.getState() == flag_state.ENABLED
+                        ? "true" : "false";
+                defaultMap.get(flag.getNamespace()).put(flagName, flagValue);
             }
         } catch (IOException e) {
             Slog.e(LOG_TAG, "failed to parse protobuf", e);
@@ -443,6 +438,13 @@
         return names;
     }
 
+    @Nullable
+    public Map<String, Map<String, String>> getAconfigDefaultValues() {
+        synchronized (mLock) {
+            return mNamespaceDefaults;
+        }
+    }
+
     // The settings provider must hold its lock when calling here.
     public Setting getSettingLocked(String name) {
         if (TextUtils.isEmpty(name)) {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 24625ea..e55bbec 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -30,6 +30,8 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.Map;
 
 public class SettingsStateTest extends AndroidTestCase {
     public static final String CRAZY_STRING =
@@ -93,7 +95,6 @@
         SettingsState settingsState = new SettingsState(
                 getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
         parsed_flags flags = parsed_flags
                 .newBuilder()
                 .addParsedFlag(parsed_flag
@@ -117,18 +118,13 @@
                 .build();
 
         synchronized (lock) {
-            settingsState.loadAconfigDefaultValues(flags.toByteArray());
-            settingsState.persistSettingsLocked();
-        }
-        settingsState.waitForHandler();
+            Map<String, Map<String, String>> defaults = new HashMap<>();
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+            Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+            assertEquals(2, namespaceDefaults.keySet().size());
 
-        synchronized (lock) {
-            assertEquals("false",
-                    settingsState.getSettingLocked(
-                        "test_namespace/com.android.flags.flag1").getValue());
-            assertEquals("true",
-                    settingsState.getSettingLocked(
-                        "test_namespace/com.android.flags.flag2").getValue());
+            assertEquals("false", namespaceDefaults.get("test_namespace/com.android.flags.flag1"));
+            assertEquals("true", namespaceDefaults.get("test_namespace/com.android.flags.flag2"));
         }
     }
 
@@ -150,21 +146,18 @@
                 .build();
 
         synchronized (lock) {
-            settingsState.loadAconfigDefaultValues(flags.toByteArray());
-            settingsState.persistSettingsLocked();
-        }
-        settingsState.waitForHandler();
+            Map<String, Map<String, String>> defaults = new HashMap<>();
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
 
-        synchronized (lock) {
-            assertEquals(null,
-                    settingsState.getSettingLocked(
-                        "test_namespace/com.android.flags.flag1").getValue());
+            Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+            assertEquals(null, namespaceDefaults);
         }
     }
 
     public void testInvalidAconfigProtoDoesNotCrash() {
+        Map<String, Map<String, String>> defaults = new HashMap<>();
         SettingsState settingsState = getSettingStateObject();
-        settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+        settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
     }
 
     public void testIsBinary() {
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index f7b1a26..7ba889b 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -36,3 +36,10 @@
     description: "Animates the floating menu's transition between curved and jagged edges."
     bug: "281140482"
 }
+
+flag {
+    name: "create_windowless_window_magnifier"
+    namespace: "accessibility"
+    description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
+    bug: "280992417"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 91a4d2e..c8e18d7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,6 +20,7 @@
 import android.os.Bundle
 import android.util.SizeF
 import android.widget.FrameLayout
+import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
@@ -38,6 +39,7 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -58,17 +60,22 @@
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
@@ -86,6 +93,9 @@
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
+import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.res.R
@@ -104,22 +114,59 @@
     var toolbarSize: IntSize? by remember { mutableStateOf(null) }
     var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
     var isDraggingToRemove by remember { mutableStateOf(false) }
+    val gridState = rememberLazyGridState()
+    val contentListState = rememberContentListState(communalContent, viewModel)
+    val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
+    val selectedIndex = viewModel.selectedIndex.collectAsState()
+    val removeButtonEnabled by remember {
+        derivedStateOf { selectedIndex.value != null || reorderingWidgets }
+    }
+
+    val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
+    val contentOffset = beforeContentPadding(contentPadding).toOffset()
 
     Box(
         modifier =
-            modifier.fillMaxSize().background(LocalAndroidColorScheme.current.outlineVariant),
+            modifier
+                .fillMaxSize()
+                .background(LocalAndroidColorScheme.current.outlineVariant)
+                .pointerInput(gridState, contentOffset, contentListState) {
+                    // If not in edit mode, don't allow selecting items.
+                    if (!viewModel.isEditMode) return@pointerInput
+                    observeTapsWithoutConsuming { offset ->
+                        val adjustedOffset = offset - contentOffset
+                        val index =
+                            gridState.layoutInfo.visibleItemsInfo
+                                .firstItemAtOffset(adjustedOffset)
+                                ?.index
+                        val newIndex =
+                            if (index?.let(contentListState::isItemEditable) == true) {
+                                index
+                            } else {
+                                null
+                            }
+                        viewModel.setSelectedIndex(newIndex)
+                    }
+                },
     ) {
         CommunalHubLazyGrid(
             communalContent = communalContent,
             viewModel = viewModel,
-            contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
+            contentPadding = contentPadding,
+            contentOffset = contentOffset,
             setGridCoordinates = { gridCoordinates = it },
-            updateDragPositionForRemove = {
+            updateDragPositionForRemove = { offset ->
                 isDraggingToRemove =
-                    checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
+                    isPointerWithinCoordinates(
+                        offset = gridCoordinates?.let { it.positionInWindow() + offset },
+                        containerToCheck = removeButtonCoordinates
+                    )
                 isDraggingToRemove
             },
             onOpenWidgetPicker = onOpenWidgetPicker,
+            gridState = gridState,
+            contentListState = contentListState,
+            selectedIndex = selectedIndex
         )
 
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -129,6 +176,14 @@
                 setRemoveButtonCoordinates = { removeButtonCoordinates = it },
                 onEditDone = onEditDone,
                 onOpenWidgetPicker = onOpenWidgetPicker,
+                onRemoveClicked = {
+                    selectedIndex.value?.let { index ->
+                        contentListState.onRemove(index)
+                        contentListState.onSaveList()
+                        viewModel.setSelectedIndex(null)
+                    }
+                },
+                removeEnabled = removeButtonEnabled
             )
         } else {
             IconButton(onClick = viewModel::onOpenWidgetEditor) {
@@ -158,16 +213,18 @@
     communalContent: List<CommunalContentModel>,
     viewModel: BaseCommunalViewModel,
     contentPadding: PaddingValues,
+    selectedIndex: State<Int?>,
+    contentOffset: Offset,
+    gridState: LazyGridState,
+    contentListState: ContentListState,
     setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
     updateDragPositionForRemove: (offset: Offset) -> Boolean,
     onOpenWidgetPicker: (() -> Unit)? = null,
 ) {
     var gridModifier = Modifier.align(Alignment.CenterStart)
-    val gridState = rememberLazyGridState()
     var list = communalContent
     var dragDropState: GridDragDropState? = null
     if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
-        val contentListState = rememberContentListState(list, viewModel)
         list = contentListState.list
         // for drag & drop operations within the communal hub grid
         dragDropState =
@@ -179,7 +236,7 @@
         gridModifier =
             gridModifier
                 .fillMaxSize()
-                .dragContainer(dragDropState, beforeContentPadding(contentPadding), viewModel)
+                .dragContainer(dragDropState, contentOffset, viewModel)
                 .onGloballyPositioned { setGridCoordinates(it) }
         // for widgets dropped from other activities
         val dragAndDropTargetState =
@@ -218,8 +275,10 @@
                     list[index].size.dp().value,
                 )
             if (viewModel.isEditMode && dragDropState != null) {
+                val selected by remember(index) { derivedStateOf { index == selectedIndex.value } }
                 DraggableItem(
                     dragDropState = dragDropState,
+                    selected = selected,
                     enabled = list[index] is CommunalContentModel.Widget,
                     index = index,
                     size = size
@@ -253,11 +312,19 @@
 @Composable
 private fun Toolbar(
     isDraggingToRemove: Boolean,
+    removeEnabled: Boolean,
+    onRemoveClicked: () -> Unit,
     setToolbarSize: (toolbarSize: IntSize) -> Unit,
     setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit,
     onOpenWidgetPicker: () -> Unit,
-    onEditDone: () -> Unit,
+    onEditDone: () -> Unit
 ) {
+    val removeButtonAlpha: Float by
+        animateFloatAsState(
+            targetValue = if (removeEnabled) 1f else 0.5f,
+            label = "RemoveButtonAlphaAnimation"
+        )
+
     Row(
         modifier =
             Modifier.fillMaxWidth()
@@ -301,13 +368,18 @@
             }
         } else {
             OutlinedButton(
-                // Button is disabled to make it non-clickable
-                enabled = false,
-                onClick = {},
-                colors = ButtonDefaults.outlinedButtonColors(disabledContentColor = colors.primary),
+                enabled = removeEnabled,
+                onClick = onRemoveClicked,
+                colors =
+                    ButtonDefaults.outlinedButtonColors(
+                        contentColor = colors.primary,
+                        disabledContentColor = colors.primary
+                    ),
                 border = BorderStroke(width = 1.0.dp, color = colors.primary),
                 contentPadding = Dimensions.ButtonPadding,
-                modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+                modifier =
+                    Modifier.graphicsLayer { alpha = removeButtonAlpha }
+                        .onGloballyPositioned { setRemoveButtonCoordinates(it) }
             ) {
                 RemoveButtonContent(spacerModifier)
             }
@@ -385,7 +457,7 @@
 ) {
     when (model) {
         is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
-        is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
+        is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(size)
         is CommunalContentModel.CtaTileInViewMode ->
             CtaTileInViewModeContent(viewModel, size, modifier)
         is CommunalContentModel.CtaTileInEditMode ->
@@ -396,11 +468,11 @@
     }
 }
 
-/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+/** Creates an empty card used to highlight a particular spot on the grid. */
 @Composable
-fun WidgetPlaceholderContent(size: SizeF) {
+fun HighlightedItem(size: SizeF, modifier: Modifier = Modifier) {
     Card(
-        modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+        modifier = modifier.size(Dp(size.width), Dp(size.height)),
         colors = CardDefaults.cardColors(containerColor = Color.Transparent),
         border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
         shape = RoundedCornerShape(16.dp)
@@ -528,7 +600,7 @@
         contentAlignment = Alignment.Center,
     ) {
         AndroidView(
-            modifier = modifier,
+            modifier = modifier.allowGestures(allowed = !viewModel.isEditMode),
             factory = { context ->
                 // The AppWidgetHostView will inherit the interaction handler from the
                 // AppWidgetHost. So set the interaction handler here before creating the view, and
@@ -616,8 +688,8 @@
 private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx {
     return with(LocalDensity.current) {
         ContentPaddingInPx(
-            startPadding = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
-            topPadding = paddingValues.calculateTopPadding().toPx()
+            start = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
+            top = paddingValues.calculateTopPadding().toPx()
         )
     }
 }
@@ -626,18 +698,15 @@
  * Check whether the pointer position that the item is being dragged at is within the coordinates of
  * the remove button in the toolbar. Returns true if the item is removable.
  */
-private fun checkForDraggingToRemove(
-    offset: Offset,
-    removeButtonCoordinates: LayoutCoordinates?,
-    gridCoordinates: LayoutCoordinates?,
+private fun isPointerWithinCoordinates(
+    offset: Offset?,
+    containerToCheck: LayoutCoordinates?
 ): Boolean {
-    if (removeButtonCoordinates == null || gridCoordinates == null) {
+    if (offset == null || containerToCheck == null) {
         return false
     }
-    val pointer = gridCoordinates.positionInWindow() + offset
-    val removeButton = removeButtonCoordinates.positionInWindow()
-    return pointer.x in removeButton.x..removeButton.x + removeButtonCoordinates.size.width &&
-        pointer.y in removeButton.y..removeButton.y + removeButtonCoordinates.size.height
+    val container = containerToCheck.boundsInWindow()
+    return container.contains(offset)
 }
 
 private fun CommunalContentSize.dp(): Dp {
@@ -648,7 +717,9 @@
     }
 }
 
-data class ContentPaddingInPx(val startPadding: Float, val topPadding: Float)
+data class ContentPaddingInPx(val start: Float, val top: Float) {
+    fun toOffset(): Offset = Offset(start, top)
+}
 
 object Dimensions {
     val CardWidth = 464.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 979991d..45f98b8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -21,12 +21,12 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.toMutableStateList
 import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 
 @Composable
 fun rememberContentListState(
     communalContent: List<CommunalContentModel>,
-    viewModel: CommunalEditModeViewModel,
+    viewModel: BaseCommunalViewModel,
 ): ContentListState {
     return remember(communalContent) {
         ContentListState(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 1138221..a195953 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -17,6 +17,10 @@
 package com.android.systemui.communal.ui.compose
 
 import android.util.SizeF
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
 import androidx.compose.foundation.gestures.scrollBy
@@ -32,6 +36,7 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
@@ -39,6 +44,7 @@
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
 import com.android.systemui.communal.ui.compose.extensions.plus
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import kotlinx.coroutines.CoroutineScope
@@ -109,13 +115,10 @@
 
     internal fun onDragStart(offset: Offset, contentOffset: Offset) {
         state.layoutInfo.visibleItemsInfo
-            .firstOrNull { item ->
-                // grid item offset is based off grid content container so we need to deduct
-                // before content padding from the initial pointer position
-                contentListState.isItemEditable(item.index) &&
-                    (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
-                    (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
-            }
+            .filter { item -> contentListState.isItemEditable(item.index) }
+            // grid item offset is based off grid content container so we need to deduct
+            // before content padding from the initial pointer position
+            .firstItemAtOffset(offset - contentOffset)
             ?.apply {
                 dragStartPointerOffset = offset - this.offset.toOffset()
                 draggingItemIndex = index
@@ -148,12 +151,11 @@
         val middleOffset = startOffset + (endOffset - startOffset) / 2f
 
         val targetItem =
-            state.layoutInfo.visibleItemsInfo.find { item ->
-                contentListState.isItemEditable(item.index) &&
-                    middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
-                    middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
-                    draggingItem.index != item.index
-            }
+            state.layoutInfo.visibleItemsInfo
+                .asSequence()
+                .filter { item -> contentListState.isItemEditable(item.index) }
+                .filter { item -> draggingItem.index != item.index }
+                .firstItemAtOffset(middleOffset)
 
         if (targetItem != null) {
             val scrollToIndex =
@@ -208,32 +210,31 @@
 
 fun Modifier.dragContainer(
     dragDropState: GridDragDropState,
-    beforeContentPadding: ContentPaddingInPx,
+    contentOffset: Offset,
     viewModel: BaseCommunalViewModel,
 ): Modifier {
-    return pointerInput(dragDropState, beforeContentPadding) {
-        detectDragGesturesAfterLongPress(
-            onDrag = { change, offset ->
-                change.consume()
-                dragDropState.onDrag(offset = offset)
-            },
-            onDragStart = { offset ->
-                dragDropState.onDragStart(
-                    offset,
-                    Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding)
-                )
-                viewModel.onReorderWidgetStart()
-            },
-            onDragEnd = {
-                dragDropState.onDragInterrupted()
-                viewModel.onReorderWidgetEnd()
-            },
-            onDragCancel = {
-                dragDropState.onDragInterrupted()
-                viewModel.onReorderWidgetCancel()
-            }
-        )
-    }
+    return this.then(
+        pointerInput(dragDropState, contentOffset) {
+            detectDragGesturesAfterLongPress(
+                onDrag = { change, offset ->
+                    change.consume()
+                    dragDropState.onDrag(offset = offset)
+                },
+                onDragStart = { offset ->
+                    dragDropState.onDragStart(offset, contentOffset)
+                    viewModel.onReorderWidgetStart()
+                },
+                onDragEnd = {
+                    dragDropState.onDragInterrupted()
+                    viewModel.onReorderWidgetEnd()
+                },
+                onDragCancel = {
+                    dragDropState.onDragInterrupted()
+                    viewModel.onReorderWidgetCancel()
+                }
+            )
+        }
+    )
 }
 
 /** Wrap LazyGrid item with additional modifier needed for drag and drop. */
@@ -243,6 +244,7 @@
     dragDropState: GridDragDropState,
     index: Int,
     enabled: Boolean,
+    selected: Boolean,
     size: SizeF,
     modifier: Modifier = Modifier,
     content: @Composable (isDragging: Boolean) -> Unit
@@ -250,21 +252,31 @@
     if (!enabled) {
         return Box(modifier = modifier) { content(false) }
     }
+
     val dragging = index == dragDropState.draggingItemIndex
+    val itemAlpha: Float by
+        animateFloatAsState(
+            targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f,
+            label = "DraggableItemAlpha"
+        )
     val draggingModifier =
         if (dragging) {
             Modifier.zIndex(1f).graphicsLayer {
                 translationX = dragDropState.draggingItemOffset.x
                 translationY = dragDropState.draggingItemOffset.y
-                alpha = if (dragDropState.isDraggingToRemove) 0.5f else 1f
+                alpha = itemAlpha
             }
         } else {
             Modifier.animateItemPlacement()
         }
 
     Box(modifier) {
-        if (dragging) {
-            WidgetPlaceholderContent(size)
+        AnimatedVisibility(
+            visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
+            enter = fadeIn(),
+            exit = fadeOut()
+        ) {
+            HighlightedItem(size)
         }
         Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
new file mode 100644
index 0000000..132093f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.toRect
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Iterable<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+    firstOrNull { item ->
+        isItemAtOffset(item, offset)
+    }
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Sequence<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+    firstOrNull { item ->
+        isItemAtOffset(item, offset)
+    }
+
+private fun isItemAtOffset(item: LazyGridItemInfo, offset: Offset): Boolean {
+    val boundingBox = IntRect(item.offset, item.size)
+    return boundingBox.toRect().contains(offset)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt
index 252945f..b31008e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.communal.ui.compose.extensions
 
-import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
-import javax.inject.Qualifier
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
 
-/** Detailed [DeviceBasedSatelliteRepository] logs */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class OemSatelliteInputLog
+/** Sets whether gestures are allowed on children of this element. */
+fun Modifier.allowGestures(allowed: Boolean): Modifier =
+    if (allowed) {
+        this
+    } else {
+        this.then(pointerInput(Unit) { consumeAllGestures() })
+    }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
new file mode 100644
index 0000000..1407494
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * presses are excluded.
+ */
+suspend fun PointerInputScope.observeTapsWithoutConsuming(
+    pass: PointerEventPass = PointerEventPass.Initial,
+    onTap: ((Offset) -> Unit)? = null,
+) = coroutineScope {
+    if (onTap == null) return@coroutineScope
+    awaitEachGesture {
+        awaitFirstDown(pass = pass)
+        val tapTimeout = viewConfiguration.longPressTimeoutMillis
+        val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
+        if (up != null) {
+            onTap(up.position)
+        }
+    }
+}
+
+/** Consume all gestures on the initial pass so that child elements do not receive them. */
+suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
+    awaitEachGesture {
+        awaitPointerEvent(pass = PointerEventPass.Initial)
+            .changes
+            .forEach(PointerInputChange::consume)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 4f7d944..6828041 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -21,23 +21,24 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
-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
 
@@ -46,18 +47,11 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
 
-    private lateinit var underTest: KeyguardTransitionInteractor
-    private lateinit var repository: FakeKeyguardTransitionRepository
-    private val testScope = TestScope()
+    val kosmos = testKosmos()
 
-    @Before
-    fun setUp() {
-        repository = FakeKeyguardTransitionRepository()
-        underTest = KeyguardTransitionInteractorFactory.create(
-                scope = testScope.backgroundScope,
-                repository = repository,
-        ).keyguardTransitionInteractor
-    }
+    val underTest = kosmos.keyguardTransitionInteractor
+    val repository = kosmos.fakeKeyguardTransitionRepository
+    val testScope = kosmos.testScope
 
     @Test
     fun transitionCollectorsReceivesOnlyAppropriateEvents() = runTest {
@@ -114,48 +108,50 @@
     }
 
     @Test
-    fun finishedKeyguardStateTests() = testScope.runTest {
-        val finishedSteps by collectValues(underTest.finishedKeyguardState)
-        runCurrent()
-        val steps = mutableListOf<TransitionStep>()
-
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
-        steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
-        steps.forEach {
-            repository.sendTransitionStep(it)
+    fun finishedKeyguardStateTests() =
+        testScope.runTest {
+            val finishedSteps by collectValues(underTest.finishedKeyguardState)
             runCurrent()
-        }
+            val steps = mutableListOf<TransitionStep>()
 
-        assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
-    }
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
+        }
 
     @Test
-    fun startedKeyguardStateTests() = testScope.runTest {
-        val startedStates by collectValues(underTest.startedKeyguardState)
-        runCurrent()
-        val steps = mutableListOf<TransitionStep>()
-
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
-        steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
-        steps.forEach {
-            repository.sendTransitionStep(it)
+    fun startedKeyguardStateTests() =
+        testScope.runTest {
+            val startedStates by collectValues(underTest.startedKeyguardState)
             runCurrent()
-        }
+            val steps = mutableListOf<TransitionStep>()
 
-        assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
-    }
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
+        }
 
     @Test
     fun finishedKeyguardTransitionStepTests() = runTest {
@@ -178,7 +174,7 @@
 
         // Ignore the default state.
         assertThat(finishedSteps.subList(1, finishedSteps.size))
-                .isEqualTo(listOf(steps[2], steps[5]))
+            .isEqualTo(listOf(steps[2], steps[5]))
     }
 
     @Test
@@ -233,500 +229,1067 @@
     }
 
     @Test
-    fun isInTransitionToState() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionToState(GONE))
+    fun isInTransitionToAnyState() =
+        testScope.runTest {
+            val inTransition by collectValues(underTest.isInTransitionToAnyState)
 
-        sendSteps(
+            assertEquals(
+                listOf(
+                    true, // The repo is seeded with a transition from OFF to LOCKSCREEN.
+                    false,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+        }
+
+    @Test
+    fun isInTransitionToAnyState_finishedStateIsStartedStateAfterCancels() =
+        testScope.runTest {
+            val inTransition by collectValues(underTest.isInTransitionToAnyState)
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+
+            // Start FINISHED in GONE.
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+                TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+                TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+                TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                    // We should have been in transition throughout the entire transition, including
+                    // both cancellations, and we should still be in transition despite now
+                    // transitioning to GONE, the state we're also FINISHED in.
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+        }
+
+    @Test
+    fun isInTransitionToState() =
+        testScope.runTest {
+            val results by collectValues(underTest.isInTransitionToState(GONE))
+
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isInTransitionFromState() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionFromState(DOZING))
+    fun isInTransitionFromState() =
+        testScope.runTest {
+            val results by collectValues(underTest.isInTransitionFromState(DOZING))
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isInTransitionFromStateWhere() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionFromStateWhere {
-            it == DOZING
-        })
+    fun isInTransitionFromStateWhere() =
+        testScope.runTest {
+            val results by collectValues(underTest.isInTransitionFromStateWhere { it == DOZING })
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isInTransitionWhere() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionWhere(
-            fromStatePredicate = { it == DOZING },
-            toStatePredicate = { it == GONE },
-        ))
+    fun isInTransitionWhere() =
+        testScope.runTest {
+            val results by
+                collectValues(
+                    underTest.isInTransitionWhere(
+                        fromStatePredicate = { it == DOZING },
+                        toStatePredicate = { it == GONE },
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isFinishedInStateWhere() = testScope.runTest {
-        val results by collectValues(underTest.isFinishedInStateWhere { it == GONE } )
+    fun isInTransitionWhere_withCanceledStep() =
+        testScope.runTest {
+            val results by
+                collectValues(
+                    underTest.isInTransitionWhere(
+                        fromStatePredicate = { it == DOZING },
+                        toStatePredicate = { it == GONE },
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false, // Finished in DOZING, not GONE.
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, STARTED),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, RUNNING),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, CANCELED),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
-        )
+                TransitionStep(GONE, DOZING, 1f, FINISHED),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
-
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
-
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
-
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isFinishedInState() = testScope.runTest {
-        val results by collectValues(underTest.isFinishedInState(GONE))
+    fun isFinishedInStateWhere() =
+        testScope.runTest {
+            val results by collectValues(underTest.isFinishedInStateWhere { it == GONE })
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false, // Finished in DOZING, not GONE.
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false, // Finished in DOZING, not GONE.
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+            sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+            sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+            sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = testScope.runTest {
-        val finishedStates by collectValues(underTest.finishedKeyguardState)
+    fun isFinishedInState() =
+        testScope.runTest {
+            val results by collectValues(underTest.isFinishedInState(GONE))
 
-        // We default FINISHED in LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN
-        ), finishedStates)
+            sendSteps(
+                TransitionStep(AOD, DOZING, 0f, STARTED),
+                TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+                TransitionStep(AOD, DOZING, 1f, FINISHED),
+            )
 
-        sendSteps(
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false, // Finished in DOZING, not GONE.
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0f, STARTED),
+                TransitionStep(GONE, DOZING, 0f, RUNNING),
+            )
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
+
+            sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
+
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, STARTED),
+                TransitionStep(DOZING, GONE, 0f, RUNNING),
+            )
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
+
+    @Test
+    fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
+        testScope.runTest {
+            val finishedStates by collectValues(underTest.finishedKeyguardState)
+
+            // We default FINISHED in LOCKSCREEN.
+            assertEquals(listOf(LOCKSCREEN), finishedStates)
+
+            sendSteps(
                 TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
                 TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
                 TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED),
-        )
+            )
 
-        // We're FINISHED in AOD.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-        ), finishedStates)
+            // We're FINISHED in AOD.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                ),
+                finishedStates
+            )
 
-        // Transition back to LOCKSCREEN.
-        sendSteps(
+            // Transition back to LOCKSCREEN.
+            sendSteps(
                 TransitionStep(AOD, LOCKSCREEN, 0f, STARTED),
                 TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING),
                 TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED),
-        )
+            )
 
-        // We're FINISHED in LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-        ), finishedStates)
+            // We're FINISHED in LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
                 TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
-        )
+            )
 
-        // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
-        // LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-        ), finishedStates)
+            // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
+            // LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED),
-        )
+            )
 
-        // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-        ), finishedStates)
+            // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED),
                 TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING),
                 TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED),
-        )
+            )
 
-        // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
-        // LOCKSCREEN after the cancellation.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-                LOCKSCREEN,
-        ), finishedStates)
-    }
+            // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
+            // LOCKSCREEN after the cancellation.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
+        }
+
+    @Test
+    fun testCurrentState() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.currentKeyguardState)
+
+            // We init the repo with a transition from OFF -> LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
+            )
+
+            // The current state should continue to be LOCKSCREEN as we transition to AOD.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
+            )
+
+            // The current state should continue to be LOCKSCREEN as we transition to AOD.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, AOD, 0.6f, CANCELED),
+            )
+
+            // Once CANCELED, we're still currently in LOCKSCREEN...
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(AOD, LOCKSCREEN, 0.6f, STARTED),
+            )
+
+            // ...until STARTING back to LOCKSCREEN, at which point the "current" state should be
+            // the
+            // one we're transitioning from, despite never FINISHING in that state.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    AOD,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(AOD, LOCKSCREEN, 0.8f, RUNNING),
+                TransitionStep(AOD, LOCKSCREEN, 0.8f, FINISHED),
+            )
+
+            // FINSHING in LOCKSCREEN should update the current state to LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+        }
+
+    @Test
+    fun testCurrentState_multipleCancellations_backToLastFinishedState() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.currentKeyguardState)
+
+            // We init the repo with a transition from OFF -> LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    // Default transition from OFF -> LOCKSCREEN
+                    OFF,
+                    LOCKSCREEN,
+                    // Transitioned to GONE
+                    GONE,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0f, STARTED),
+                TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+                TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    // Current state should not be DOZING until the post-cancelation transition is
+                    // STARTED
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    // DOZING -> LS STARTED, DOZING is now the current state.
+                    DOZING,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+                TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    DOZING,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    DOZING,
+                    // LS -> GONE STARTED, LS is now the current state.
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    DOZING,
+                    LOCKSCREEN,
+                    // FINISHED in GONE, GONE is now the current state.
+                    GONE,
+                ),
+                currentStates
+            )
+        }
 
     private suspend fun sendSteps(vararg steps: TransitionStep) {
         steps.forEach {
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
deleted file mode 100644
index 045c19e..0000000
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
+++ /dev/null
@@ -1,49 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
-        android:fillAlpha="0.3"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
-        android:fillAlpha="0.3"
-        android:fillColor="#fff"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
deleted file mode 100644
index 5e012ab..0000000
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
+++ /dev/null
@@ -1,49 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
-        android:fillAlpha="0.3"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
-        android:fillColor="#fff"/>
-</vector>
-
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
deleted file mode 100644
index d8a9a70..0000000
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
+++ /dev/null
@@ -1,47 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
-        android:fillColor="#fff"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
deleted file mode 100644
index dec9930..0000000
--- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
+++ /dev/null
@@ -1,42 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0"
-    >
-    <path
-        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
-        android:fillColor="#fff"/>
-    <path
-        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
-        android:fillColor="#fff"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/bindable_status_bar_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
deleted file mode 100644
index ee4d05c..0000000
--- a/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
+++ /dev/null
@@ -1,34 +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.
-  -->
-
-<!-- Base layout that provides a single bindable icon_view id image view -->
-<com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    >
-
-    <ImageView
-        android:id="@+id/icon_view"
-        android:layout_height="@dimen/status_bar_bindable_icon_size"
-        android:layout_width="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:padding="@dimen/status_bar_bindable_icon_padding"
-        android:scaleType="fitCenter"
-        />
-
-</com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9a4520c..798fc06b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -151,8 +151,6 @@
     <dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen>
     <!-- Original dp height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
-    <dimen name="status_bar_bindable_icon_size">20sp</dimen>
-    <dimen name="status_bar_bindable_icon_padding">2sp</dimen>
 
     <!-- Default horizontal drawable padding for status bar icons. -->
     <dimen name="status_bar_horizontal_padding">2.5sp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a5a545a..033f93b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -3,6 +3,7 @@
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -191,11 +192,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-
-        mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
-        mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
-        mStatusArea = findViewById(R.id.keyguard_status_area);
-
+        if (!migrateClocksToBlueprint()) {
+            mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
+            mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
+            mStatusArea = findViewById(R.id.keyguard_status_area);
+        }
         onConfigChanged();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index d372f5a..1758831 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -494,7 +494,7 @@
             boolean shouldBeCentered,
             boolean animate) {
         if (migrateClocksToBlueprint()) {
-            mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
+            mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
         } else {
             mKeyguardClockSwitchController.setSplitShadeCentered(
                     splitShadeEnabled && shouldBeCentered);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 22b02da..16e7f05 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -20,9 +20,9 @@
 import android.content.res.Resources
 import android.content.res.Resources.Theme
 import android.graphics.Paint
-import android.hardware.biometrics.PromptContentListItem
-import android.hardware.biometrics.PromptContentListItemBulletedText
-import android.hardware.biometrics.PromptContentListItemPlainText
+import android.hardware.biometrics.PromptContentItem
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptContentItemPlainText
 import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptVerticalListContentView
 import android.text.SpannableString
@@ -111,21 +111,21 @@
     return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout
 }
 
-private fun PromptContentListItem.doesExceedMaxLinesIfTwoColumn(
+private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
     resources: Resources,
 ): Boolean {
     val passedInText: CharSequence =
         when (this) {
-            is PromptContentListItemPlainText -> text
-            is PromptContentListItemBulletedText -> text
+            is PromptContentItemPlainText -> text
+            is PromptContentItemBulletedText -> text
             else -> {
-                throw IllegalStateException("No such ListItem: $this")
+                throw IllegalStateException("No such PromptContentItem: $this")
             }
         }
 
     when (this) {
-        is PromptContentListItemPlainText,
-        is PromptContentListItemBulletedText -> {
+        is PromptContentItemPlainText,
+        is PromptContentItemBulletedText -> {
             val dialogMargin =
                 resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
             val halfDialogWidth =
@@ -155,12 +155,12 @@
             return numLines > maxLines
         }
         else -> {
-            throw IllegalStateException("No such ListItem: $this")
+            throw IllegalStateException("No such PromptContentItem: $this")
         }
     }
 }
 
-private fun PromptContentListItem.toView(
+private fun PromptContentItem.toView(
     resources: Resources,
     inflater: LayoutInflater,
     theme: Theme,
@@ -171,10 +171,10 @@
     textView.layoutParams = lp
 
     when (this) {
-        is PromptContentListItemPlainText -> {
+        is PromptContentItemPlainText -> {
             textView.text = text
         }
-        is PromptContentListItemBulletedText -> {
+        is PromptContentItemBulletedText -> {
             val bulletedText = SpannableString(text)
             val span =
                 BulletSpan(
@@ -186,25 +186,25 @@
             textView.text = bulletedText
         }
         else -> {
-            throw IllegalStateException("No such ListItem: $this")
+            throw IllegalStateException("No such PromptContentItem: $this")
         }
     }
     return textView
 }
 
-private fun PromptContentListItem.getListItemPadding(resources: Resources): Int {
+private fun PromptContentItem.getListItemPadding(resources: Resources): Int {
     var listItemPadding =
         resources.getDimensionPixelSize(
             R.dimen.biometric_prompt_content_list_item_padding_horizontal
         ) * 2
     when (this) {
-        is PromptContentListItemPlainText -> {}
-        is PromptContentListItemBulletedText -> {
+        is PromptContentItemPlainText -> {}
+        is PromptContentItemBulletedText -> {
             listItemPadding +=
                 getListItemBulletRadius(resources) * 2 + getListItemBulletGapWidth(resources)
         }
         else -> {
-            throw IllegalStateException("No such ListItem: $this")
+            throw IllegalStateException("No such PromptContentItem: $this")
         }
     }
     return listItemPadding
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 84708a4..4da348e 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
@@ -24,6 +24,7 @@
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.media.controls.ui.MediaHost
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
 
@@ -36,6 +37,15 @@
 
     val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
 
+    /** Whether widgets are currently being re-ordered. */
+    open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
+
+    private val _selectedIndex: MutableStateFlow<Int?> = MutableStateFlow(null)
+
+    /** The index of the currently selected item, or null if no item selected. */
+    val selectedIndex: StateFlow<Int?>
+        get() = _selectedIndex
+
     fun onSceneChanged(scene: CommunalSceneKey) {
         communalInteractor.onSceneChanged(scene)
     }
@@ -105,4 +115,9 @@
 
     /** Called as the user cancels dragging a widget to reorder. */
     open fun onReorderWidgetCancel() {}
+
+    /** Set the index of the currently selected item */
+    fun setSelectedIndex(index: Int?) {
+        _selectedIndex.value = index
+    }
 }
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 7faf653..fcad45f 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
@@ -37,7 +37,10 @@
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 /** The view model for communal hub in edit mode. */
 @SysUISingleton
@@ -69,9 +72,15 @@
 
     // Only widgets are editable. The CTA tile comes last in the list and remains visible.
     override val communalContent: Flow<List<CommunalContentModel>> =
-        communalInteractor.widgetContent.map { widgets ->
-            widgets + listOf(CommunalContentModel.CtaTileInEditMode())
-        }
+        communalInteractor.widgetContent
+            // Clear the selected index when the list is updated.
+            .onEach { setSelectedIndex(null) }
+            .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+
+    private val _reorderingWidgets = MutableStateFlow(false)
+
+    override val reorderingWidgets: StateFlow<Boolean>
+        get() = _reorderingWidgets
 
     override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
 
@@ -135,14 +144,19 @@
     }
 
     override fun onReorderWidgetStart() {
+        // Clear selection status
+        setSelectedIndex(null)
+        _reorderingWidgets.value = true
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
     }
 
     override fun onReorderWidgetEnd() {
+        _reorderingWidgets.value = false
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
     }
 
     override fun onReorderWidgetCancel() {
+        _reorderingWidgets.value = false
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b2d7052..6d9994f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -556,7 +556,7 @@
     @Provides
     @Singleton
     static SubscriptionManager provideSubscriptionManager(Context context) {
-        return context.getSystemService(SubscriptionManager.class);
+        return context.getSystemService(SubscriptionManager.class).createForAllUserProfiles();
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
new file mode 100644
index 0000000..afe9151
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceRepository @Inject constructor() {
+    private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE)
+    val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+
+    fun setBcSmartspaceVisibility(visibility: Int) {
+        _bcSmartspaceVisibility.value = visibility
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
new file mode 100644
index 0000000..67b5745
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSmartspaceRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceInteractor
+@Inject
+constructor(private val keyguardSmartspaceRepository: KeyguardSmartspaceRepository) {
+    var bcSmartspaceVisibility: StateFlow<Int> = keyguardSmartspaceRepository.bcSmartspaceVisibility
+
+    fun setBcSmartspaceVisibility(visibility: Int) {
+        keyguardSmartspaceRepository.setBcSmartspaceVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index a8223ea..b43ab5e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -37,17 +37,19 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.shareIn
 
 /** Encapsulates business-logic related to the keyguard transitions. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardTransitionInteractor
 @Inject
@@ -192,29 +194,121 @@
     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
 
-    /** The destination state of the last started transition. */
+    /** The destination state of the last [TransitionState.STARTED] transition. */
     val startedKeyguardState: SharedFlow<KeyguardState> =
         startedKeyguardTransitionStep
             .map { step -> step.to }
             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
 
-    /** The last completed [KeyguardState] transition */
+    /**
+     * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
+     *
+     * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a
+     * value when a subsequent transition is STARTED. It will *only* emit once we have finally
+     * FINISHED in a state. This can have unintuitive implications.
+     *
+     * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in
+     * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain
+     * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition
+     * finishes (at which point we'll be FINISHED in LOCKSCREEN).
+     *
+     * Since there's no real limit to how many consecutive transitions can be canceled, it's even
+     * possible for the FINISHED state to be the same as the STARTED state while still
+     * transitioning.
+     *
+     * For example:
+     * 1. We're finished in GONE.
+     * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still
+     *    FINISHED in GONE.
+     * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING ->
+     *    LOCKSCREEN transition. We're still FINISHED in GONE.
+     * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this
+     *    starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also
+     *    STARTED a transition *to* GONE.
+     * 5. We'll emit KeyguardState.GONE again once the transition finishes.
+     *
+     * If you just need to know when we eventually settle into a state, this flow is likely
+     * sufficient. However, if you're having issues with state *during* transitions started after
+     * one or more canceled transitions, you probably need to use [currentKeyguardState].
+     */
     val finishedKeyguardState: SharedFlow<KeyguardState> =
         finishedKeyguardTransitionStep
             .map { step -> step.to }
             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
 
     /**
-     * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
-     * it.
+     * The [KeyguardState] we're currently in.
+     *
+     * If we're not in transition, this is simply the [finishedKeyguardState]. If we're in
+     * transition, this is the state we're transitioning *from*.
+     *
+     * Absent CANCELED transitions, [currentKeyguardState] and [finishedKeyguardState] are always
+     * identical - if a transition FINISHES in a given state, the subsequent state we START a
+     * transition *from* would always be that same previously FINISHED state.
+     *
+     * However, if a transition is CANCELED, the next transition will START from a state we never
+     * FINISHED in. For example, if we transition from GONE -> DOZING, but CANCEL that transition in
+     * favor of DOZING -> LOCKSCREEN, we've STARTED a transition *from* DOZING despite never
+     * FINISHING in DOZING. Thus, the current state will be DOZING but the FINISHED state will still
+     * be GONE.
+     *
+     * In this example, if there was DOZING-related state that needs to be set up in order to
+     * properly render a DOZING -> LOCKSCREEN transition, it would never be set up if we were
+     * listening for [finishedKeyguardState] to emit DOZING. However, [currentKeyguardState] would
+     * emit DOZING immediately upon STARTING DOZING -> LOCKSCREEN, allowing us to set up the state.
+     *
+     * Whether you want to use [currentKeyguardState] or [finishedKeyguardState] depends on your
+     * specific use case and how you want to handle cancellations. In general, if you're dealing
+     * with state/UI present across multiple [KeyguardState]s, you probably want
+     * [currentKeyguardState]. If you're dealing with state/UI encapsulated within a single state,
+     * you likely want [finishedKeyguardState].
+     *
+     * As an example, let's say you want to animate in a message on the lockscreen UI after waking
+     * up, and that TextView is not involved in animations between states. You'd want to collect
+     * [finishedKeyguardState], so you'll only animate it in once we're settled on the lockscreen.
+     * If you use [currentKeyguardState] in this case, a DOZING -> LOCKSCREEN transition that is
+     * interrupted by a LOCKSCREEN -> GONE transition would cause the message to become visible
+     * immediately upon LOCKSCREEN -> GONE STARTING, as the current state would become LOCKSCREEN in
+     * that case. That's likely not what you want.
+     *
+     * On the other hand, let's say you're animating the smartspace from alpha 0f to 1f during
+     * DOZING -> LOCKSCREEN, but the transition is interrupted by LOCKSCREEN -> GONE. LS -> GONE
+     * needs the smartspace to be alpha=1f so that it can play the shared-element unlock animation.
+     * In this case, we'd want to collect [currentKeyguardState] and ensure the smartspace is
+     * visible when the current state is LOCKSCREEN. If you use [finishedKeyguardState] in this
+     * case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace
+     * during the LS -> GONE transition.
+     *
+     * If you need special-case handling for cancellations (such as conditional handling depending
+     * on which [KeyguardState] was canceled) you can collect [canceledKeyguardTransitionStep]
+     * directly.
+     *
+     * As a helpful footnote, here's the values of [finishedKeyguardState] and
+     * [currentKeyguardState] during a sequence with two cancellations:
+     * 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE.
+     * 2. We START a transition from GONE -> DOZING. currentKeyguardState=GONE;
+     *    finishedKeyguardState=GONE.
+     * 3. We CANCEL this transition and START a transition from DOZING -> LOCKSCREEN.
+     *    currentKeyguardState=DOZING; finishedKeyguardState=GONE.
+     * 4. We subsequently also CANCEL DOZING -> LOCKSCREEN and START LOCKSCREEN -> GONE.
+     *    currentKeyguardState=LOCKSCREEN finishedKeyguardState=GONE.
+     * 5. LOCKSCREEN -> GONE is allowed to FINISH. currentKeyguardState=GONE;
+     *    finishedKeyguardState=GONE.
      */
-    val isInTransitionToAnyState =
-        combine(
-            startedKeyguardTransitionStep,
-            finishedKeyguardState,
-        ) { startedStep, finishedState ->
-            startedStep.to != finishedState
-        }
+    val currentKeyguardState: SharedFlow<KeyguardState> =
+        repository.transitions
+            .mapLatest {
+                if (it.transitionState == TransitionState.FINISHED) {
+                    it.to
+                } else {
+                    it.from
+                }
+            }
+            .distinctUntilChanged()
+            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
+    /** Whether we've currently STARTED a transition and haven't yet FINISHED it. */
+    val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
 
     /**
      * The amount of transition into or out of the given [KeyguardState].
@@ -304,13 +398,12 @@
         fromStatePredicate: (KeyguardState) -> Boolean,
         toStatePredicate: (KeyguardState) -> Boolean,
     ): Flow<Boolean> {
-        return combine(
-                startedKeyguardTransitionStep,
-                finishedKeyguardState,
-            ) { startedStep, finishedState ->
-                fromStatePredicate(startedStep.from) &&
-                    toStatePredicate(startedStep.to) &&
-                    finishedState != startedStep.to
+        return repository.transitions
+            .filter { it.transitionState != TransitionState.CANCELED }
+            .mapLatest {
+                it.transitionState != TransitionState.FINISHED &&
+                    fromStatePredicate(it.from) &&
+                    toStatePredicate(it.to)
             }
             .distinctUntilChanged()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index bf763b4..400b8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -30,7 +30,10 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
 import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -48,6 +51,7 @@
         keyguardRootView: ConstraintLayout,
         viewModel: KeyguardClockViewModel,
         keyguardClockInteractor: KeyguardClockInteractor,
+        blueprintInteractor: KeyguardBlueprintInteractor,
     ) {
         keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -61,18 +65,16 @@
                     viewModel.currentClock.collect { currentClock ->
                         cleanupClockViews(viewModel.clock, keyguardRootView, viewModel.burnInLayer)
                         viewModel.clock = currentClock
-                        addClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
-                        viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
-                        applyConstraints(clockSection, keyguardRootView, true)
+                        addClockViews(currentClock, keyguardRootView)
+                        updateBurnInLayer(keyguardRootView, viewModel)
+                        blueprintInteractor.refreshBlueprint()
                     }
                 }
-                // TODO: Weather clock dozing animation
-                // will trigger both shouldBeCentered and clockSize change
-                // we should avoid this
                 launch {
                     if (!migrateClocksToBlueprint()) return@launch
                     viewModel.clockSize.collect {
-                        applyConstraints(clockSection, keyguardRootView, true)
+                        updateBurnInLayer(keyguardRootView, viewModel)
+                        blueprintInteractor.refreshBlueprint()
                     }
                 }
                 launch {
@@ -82,7 +84,7 @@
                             if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
                                 playClockCenteringAnimation(clockSection, keyguardRootView, it)
                             } else {
-                                applyConstraints(clockSection, keyguardRootView, true)
+                                blueprintInteractor.refreshBlueprint()
                             }
                         }
                     }
@@ -90,6 +92,29 @@
             }
         }
     }
+    @VisibleForTesting
+    fun updateBurnInLayer(
+        keyguardRootView: ConstraintLayout,
+        viewModel: KeyguardClockViewModel,
+    ) {
+        val burnInLayer = viewModel.burnInLayer
+        val clockController = viewModel.currentClock.value
+        clockController?.let { clock ->
+            when (viewModel.clockSize.value) {
+                LARGE -> {
+                    clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) }
+                    if (clock.config.useAlternateSmartspaceAODTransition) {
+                        clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
+                    }
+                }
+                SMALL -> {
+                    clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) }
+                    clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) }
+                }
+            }
+        }
+        viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+    }
 
     private fun cleanupClockViews(
         clockController: ClockController?,
@@ -116,7 +141,6 @@
     fun addClockViews(
         clockController: ClockController?,
         rootView: ConstraintLayout,
-        burnInLayer: Layer?
     ) {
         clockController?.let { clock ->
             clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
@@ -125,17 +149,10 @@
             }
             // small clock should either be a single view or container with id
             // `lockscreen_clock_view`
-            clock.smallClock.layout.views.forEach {
-                rootView.addView(it)
-                burnInLayer?.addView(it)
-            }
+            clock.smallClock.layout.views.forEach { rootView.addView(it) }
             clock.largeClock.layout.views.forEach { rootView.addView(it) }
-            if (clock.config.useAlternateSmartspaceAODTransition) {
-                clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
-            }
         }
     }
-
     fun applyConstraints(
         clockSection: ClockSection,
         rootView: ConstraintLayout,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 81ce8f0..10392e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -16,15 +16,13 @@
 
 package com.android.systemui.keyguard.ui.binder
 
-import android.transition.TransitionManager
 import android.view.View
 import androidx.constraintlayout.helper.widget.Layer
 import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.Flags.migrateClocksToBlueprint
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,10 +33,10 @@
 object KeyguardSmartspaceViewBinder {
     @JvmStatic
     fun bind(
-        smartspaceSection: SmartspaceSection,
         keyguardRootView: ConstraintLayout,
         clockViewModel: KeyguardClockViewModel,
         smartspaceViewModel: KeyguardSmartspaceViewModel,
+        blueprintInteractor: KeyguardBlueprintInteractor,
     ) {
         keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -46,22 +44,56 @@
                     if (!migrateClocksToBlueprint()) return@launch
                     clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
                         ->
-                        if (hasCustomWeatherDataDisplay) {
-                            removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
-                        } else {
-                            addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
-                        }
-                        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
-                        val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
-                        smartspaceSection.applyConstraints(constraintSet)
-                        TransitionManager.beginDelayedTransition(keyguardRootView)
-                        constraintSet.applyTo(keyguardRootView)
+                        updateDateWeatherToBurnInLayer(
+                            keyguardRootView,
+                            clockViewModel,
+                            smartspaceViewModel
+                        )
+                        blueprintInteractor.refreshBlueprint()
+                    }
+                }
+
+                launch {
+                    smartspaceViewModel.bcSmartspaceVisibility.collect {
+                        updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
+                        blueprintInteractor.refreshBlueprint()
                     }
                 }
             }
         }
     }
 
+    private fun updateBCSmartspaceInBurnInLayer(
+        keyguardRootView: ConstraintLayout,
+        clockViewModel: KeyguardClockViewModel,
+    ) {
+        // Visibility is controlled by updateTargetVisibility in CardPagerAdapter
+        val burnInLayer = keyguardRootView.requireViewById<Layer>(R.id.burn_in_layer)
+        burnInLayer.apply {
+            val smartspaceView =
+                keyguardRootView.requireViewById<View>(sharedR.id.bc_smartspace_view)
+            if (smartspaceView.visibility == View.VISIBLE) {
+                addView(smartspaceView)
+            } else {
+                removeView(smartspaceView)
+            }
+        }
+        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+    }
+
+    private fun updateDateWeatherToBurnInLayer(
+        keyguardRootView: ConstraintLayout,
+        clockViewModel: KeyguardClockViewModel,
+        smartspaceViewModel: KeyguardSmartspaceViewModel
+    ) {
+        if (clockViewModel.hasCustomWeatherDataDisplay.value) {
+            removeDateWeatherFromBurnInLayer(keyguardRootView, smartspaceViewModel)
+        } else {
+            addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+        }
+        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+    }
+
     private fun addDateWeatherToBurnInLayer(
         constraintLayout: ConstraintLayout,
         smartspaceViewModel: KeyguardSmartspaceViewModel
@@ -76,13 +108,13 @@
                     constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
                 val weatherView =
                     constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
-                addView(weatherView)
                 addView(dateView)
+                addView(weatherView)
             }
         }
     }
 
-    private fun removeDateWeatherToBurnInLayer(
+    private fun removeDateWeatherFromBurnInLayer(
         constraintLayout: ConstraintLayout,
         smartspaceViewModel: KeyguardSmartspaceViewModel
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 24d0602..8472a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
@@ -30,11 +31,10 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeSmartspaceSection
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
@@ -62,8 +62,8 @@
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
-    smartspaceSection: SplitShadeSmartspaceSection,
-    clockSection: SplitShadeClockSection,
+    clockSection: ClockSection,
+    smartspaceSection: SmartspaceSection,
     mediaSection: SplitShadeMediaSection,
 ) : KeyguardBlueprint {
     override val id: String = ID
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
index d0626d5..fd530b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
@@ -25,6 +25,8 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.constraintlayout.helper.widget.Layer
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.res.R
 
 class BaseBlueprintTransition : TransitionSet() {
     init {
@@ -33,7 +35,16 @@
             .addTransition(ChangeBounds())
             .addTransition(AlphaInVisibility())
         excludeTarget(Layer::class.java, /* exclude= */ true)
+        excludeClockAndSmartspaceViews()
     }
+
+    private fun excludeClockAndSmartspaceViews() {
+        excludeTarget(R.id.lockscreen_clock_view, true)
+        excludeTarget(R.id.lockscreen_clock_view_large, true)
+        excludeTarget(SmartspaceView::class.java, true)
+        // TODO(b/319468190): need to exclude views from large weather clock
+    }
+
     class AlphaOutVisibility : Visibility() {
         override fun onDisappear(
             sceneRoot: ViewGroup?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
new file mode 100644
index 0000000..67a20e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
+
+class AodBurnInLayer(context: Context) : Layer(context) {
+    // For setScale in Layer class, it stores it in mScaleX/Y and directly apply scale to
+    // referenceViews instead of keeping the value in fields of View class
+    // when we try to clone ConstraintSet, it will call getScaleX from View class and return 1.0
+    // and when we clone and apply, it will reset everything in the layer
+    // which cause the flicker from AOD to LS
+    private var _scaleX = 1F
+    private var _scaleY = 1F
+    // As described for _scaleX and _scaleY, we have similar issue with translation
+    private var _translationX = 1F
+    private var _translationY = 1F
+    // avoid adding views with same ids
+    override fun addView(view: View?) {
+        view?.let { if (it.id !in referencedIds) super.addView(view) }
+    }
+    override fun setScaleX(scaleX: Float) {
+        _scaleX = scaleX
+        super.setScaleX(scaleX)
+    }
+
+    override fun getScaleX(): Float {
+        return _scaleX
+    }
+
+    override fun setScaleY(scaleY: Float) {
+        _scaleY = scaleY
+        super.setScaleY(scaleY)
+    }
+
+    override fun getScaleY(): Float {
+        return _scaleY
+    }
+
+    override fun setTranslationX(dx: Float) {
+        _translationX = dx
+        super.setTranslationX(dx)
+    }
+
+    override fun getTranslationX(): Float {
+        return _translationX
+    }
+
+    override fun setTranslationY(dy: Float) {
+        _translationY = dy
+        super.setTranslationY(dy)
+    }
+
+    override fun getTranslationY(): Float {
+        return _translationY
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 1ccc6cc..3d36eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -19,16 +19,13 @@
 
 import android.content.Context
 import android.view.View
-import androidx.constraintlayout.helper.widget.Layer
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
 import javax.inject.Inject
 
 /** Adds a layer to group elements for translation for burn-in preventation */
@@ -37,10 +34,8 @@
 constructor(
     private val context: Context,
     private val clockViewModel: KeyguardClockViewModel,
-    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
 ) : KeyguardSection() {
-    lateinit var burnInLayer: Layer
-
+    private lateinit var burnInLayer: AodBurnInLayer
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
@@ -48,7 +43,7 @@
 
         val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
         burnInLayer =
-            Layer(context).apply {
+            AodBurnInLayer(context).apply {
                 id = R.id.burn_in_layer
                 addView(nic)
                 if (!migrateClocksToBlueprint()) {
@@ -57,11 +52,6 @@
                     addView(statusView)
                 }
             }
-        if (migrateClocksToBlueprint()) {
-            // weather and date parts won't be added here, cause their visibility doesn't align
-            // with others in burnInLayer
-            addSmartspaceViews(constraintLayout)
-        }
         constraintLayout.addView(burnInLayer)
     }
 
@@ -83,14 +73,4 @@
     override fun removeViews(constraintLayout: ConstraintLayout) {
         constraintLayout.removeView(R.id.burn_in_layer)
     }
-
-    private fun addSmartspaceViews(constraintLayout: ConstraintLayout) {
-        burnInLayer.apply {
-            if (smartspaceViewModel.isSmartspaceEnabled) {
-                val smartspaceView =
-                    constraintLayout.requireViewById<View>(sharedR.id.bc_smartspace_view)
-                addView(smartspaceView)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index f560b5f..ed7abff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -30,9 +30,7 @@
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -53,13 +51,13 @@
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
     private val notificationIconAreaController: NotificationIconAreaController,
-    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     private val systemBarUtilsState: SystemBarUtilsState,
 ) : KeyguardSection() {
 
     private var nicBindingDisposable: DisposableHandle? = null
     private val nicId = R.id.aod_notification_icon_container
     private lateinit var nic: NotificationIconContainer
+    private val smartSpaceBarrier = View.generateViewId()
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!KeyguardShadeMigrationNssl.isEnabled) {
@@ -118,7 +116,7 @@
             }
         constraintSet.apply {
             if (migrateClocksToBlueprint()) {
-                connect(nicId, TOP, sharedR.id.bc_smartspace_view, BOTTOM, bottomMargin)
+                connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin)
                 setGoneMargin(nicId, BOTTOM, bottomMargin)
             } else {
                 connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b5f32c8..b344d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -23,11 +23,14 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.INVISIBLE
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
 import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
@@ -35,8 +38,10 @@
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Utils
+import dagger.Lazy
 import javax.inject.Inject
 
 internal fun ConstraintSet.setVisibility(
@@ -56,6 +61,7 @@
     protected val keyguardClockViewModel: KeyguardClockViewModel,
     private val context: Context,
     private val splitShadeStateController: SplitShadeStateController,
+    val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
 ) : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {}
 
@@ -68,6 +74,7 @@
             constraintLayout,
             keyguardClockViewModel,
             clockInteractor,
+            blueprintInteractor.get()
         )
     }
 
@@ -88,12 +95,16 @@
     ): ConstraintSet {
         // Add constraint between rootView and clockContainer
         applyDefaultConstraints(constraintSet)
+        getNonTargetClockFace(clock).applyConstraints(constraintSet)
         getTargetClockFace(clock).applyConstraints(constraintSet)
 
         // Add constraint between elements in clock and clock container
         return constraintSet.apply {
-            setAlpha(getTargetClockFace(clock).views, 1F)
-            setAlpha(getNonTargetClockFace(clock).views, 0F)
+            setVisibility(getTargetClockFace(clock).views, VISIBLE)
+            setVisibility(getNonTargetClockFace(clock).views, INVISIBLE)
+            if (!keyguardClockViewModel.useLargeClock) {
+                connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
+            }
         }
     }
 
@@ -107,9 +118,12 @@
     private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
     private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
     open fun applyDefaultConstraints(constraints: ConstraintSet) {
+        val guideline =
+            if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
+            else R.id.split_shade_guideline
         constraints.apply {
             connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
-            connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END)
+            connect(R.id.lockscreen_clock_view_large, END, guideline, END)
             connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
             var largeClockTopMargin =
                 context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index b0eee0a..8c5e9b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.Context
+import android.view.View
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
@@ -27,11 +28,9 @@
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -54,7 +53,6 @@
     ambientState: AmbientState,
     controller: NotificationStackScrollLayoutController,
     notificationStackSizeCalculator: NotificationStackSizeCalculator,
-    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     @Main mainDispatcher: CoroutineDispatcher,
 ) :
     NotificationStackScrollLayoutSection(
@@ -69,6 +67,7 @@
         notificationStackSizeCalculator,
         mainDispatcher,
     ) {
+    private val smartSpaceBarrier = View.generateViewId()
     override fun applyConstraints(constraintSet: ConstraintSet) {
         if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
@@ -76,16 +75,14 @@
         constraintSet.apply {
             val bottomMargin =
                 context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
             if (migrateClocksToBlueprint()) {
                 connect(
                     R.id.nssl_placeholder,
                     TOP,
-                    sharedR.id.bc_smartspace_view,
+                    R.id.smart_space_barrier_bottom,
                     BOTTOM,
                     bottomMargin
                 )
-                setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
             } else {
                 connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index eacd466..37842a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -18,37 +18,41 @@
 
 import android.content.Context
 import android.view.View
+import android.view.View.GONE
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.res.R
+import com.android.systemui.shared.R
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import dagger.Lazy
 import javax.inject.Inject
 
 open class SmartspaceSection
 @Inject
 constructor(
+    val context: Context,
     val keyguardClockViewModel: KeyguardClockViewModel,
     val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
-    private val context: Context,
+    val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
     val smartspaceController: LockscreenSmartspaceController,
     val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
+    val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
 ) : KeyguardSection() {
     private var smartspaceView: View? = null
     private var weatherView: View? = null
     private var dateView: View? = null
 
+    private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
+
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
             return
@@ -64,6 +68,20 @@
             }
         }
         keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
+        smartspaceVisibilityListener =
+            object : OnGlobalLayoutListener {
+                var pastVisibility = GONE
+                override fun onGlobalLayout() {
+                    smartspaceView?.let {
+                        val newVisibility = it.visibility
+                        if (pastVisibility != newVisibility) {
+                            keyguardSmartspaceInteractor.setBcSmartspaceVisibility(newVisibility)
+                            pastVisibility = newVisibility
+                        }
+                    }
+                }
+            }
+        smartspaceView?.viewTreeObserver?.addOnGlobalLayoutListener(smartspaceVisibilityListener)
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
@@ -71,10 +89,10 @@
             return
         }
         KeyguardSmartspaceViewBinder.bind(
-            this,
             constraintLayout,
             keyguardClockViewModel,
             keyguardSmartspaceViewModel,
+            blueprintInteractor.get(),
         )
     }
 
@@ -82,65 +100,96 @@
         if (!migrateClocksToBlueprint()) {
             return
         }
-        // Generally, weather should be next to dateView
-        // smartspace should be below date & weather views
         constraintSet.apply {
             // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
-            dateView?.let { dateView ->
-                constrainHeight(dateView.id, WRAP_CONTENT)
-                constrainWidth(dateView.id, WRAP_CONTENT)
-                connect(
-                    dateView.id,
-                    START,
-                    PARENT_ID,
-                    START,
-                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+            constrainHeight(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            constrainWidth(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            connect(
+                R.id.date_smartspace_view,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START,
+                context.resources.getDimensionPixelSize(
+                    com.android.systemui.res.R.dimen.below_clock_padding_start
                 )
-            }
-            weatherView?.let {
-                constrainWidth(it.id, WRAP_CONTENT)
-                dateView?.let { dateView ->
-                    connect(it.id, TOP, dateView.id, TOP)
-                    connect(it.id, BOTTOM, dateView.id, BOTTOM)
-                    connect(it.id, START, dateView.id, END, 4)
-                }
-            }
+            )
+            constrainWidth(R.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            connect(
+                R.id.weather_smartspace_view,
+                ConstraintSet.TOP,
+                R.id.date_smartspace_view,
+                ConstraintSet.TOP
+            )
+            connect(
+                R.id.weather_smartspace_view,
+                ConstraintSet.BOTTOM,
+                R.id.date_smartspace_view,
+                ConstraintSet.BOTTOM
+            )
+            connect(
+                R.id.weather_smartspace_view,
+                ConstraintSet.START,
+                R.id.date_smartspace_view,
+                ConstraintSet.END,
+                4
+            )
+
             // migrate addSmartspaceView from KeyguardClockSwitchController
-            smartspaceView?.let {
-                constrainHeight(it.id, WRAP_CONTENT)
+            constrainHeight(R.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            connect(
+                R.id.bc_smartspace_view,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START,
+                context.resources.getDimensionPixelSize(
+                    com.android.systemui.res.R.dimen.below_clock_padding_start
+                )
+            )
+            connect(
+                R.id.bc_smartspace_view,
+                ConstraintSet.END,
+                if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
+                else com.android.systemui.res.R.id.split_shade_guideline,
+                ConstraintSet.END,
+                context.resources.getDimensionPixelSize(
+                    com.android.systemui.res.R.dimen.below_clock_padding_end
+                )
+            )
+
+            if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+                clear(R.id.date_smartspace_view, ConstraintSet.TOP)
                 connect(
-                    it.id,
-                    START,
-                    PARENT_ID,
-                    START,
-                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+                    R.id.date_smartspace_view,
+                    ConstraintSet.BOTTOM,
+                    R.id.bc_smartspace_view,
+                    ConstraintSet.TOP
+                )
+            } else {
+                clear(R.id.date_smartspace_view, ConstraintSet.BOTTOM)
+                connect(
+                    R.id.date_smartspace_view,
+                    ConstraintSet.TOP,
+                    com.android.systemui.res.R.id.lockscreen_clock_view,
+                    ConstraintSet.BOTTOM
                 )
                 connect(
-                    it.id,
-                    END,
-                    PARENT_ID,
-                    END,
-                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
+                    R.id.bc_smartspace_view,
+                    ConstraintSet.TOP,
+                    R.id.date_smartspace_view,
+                    ConstraintSet.BOTTOM
                 )
             }
 
-            if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
-                dateView?.let { dateView ->
-                    smartspaceView?.let { smartspaceView ->
-                        connect(dateView.id, BOTTOM, smartspaceView.id, TOP)
-                    }
-                }
-            } else {
-                dateView?.let { dateView ->
-                    clear(dateView.id, BOTTOM)
-                    connect(dateView.id, TOP, R.id.lockscreen_clock_view, BOTTOM)
-                    constrainHeight(dateView.id, WRAP_CONTENT)
-                    smartspaceView?.let { smartspaceView ->
-                        clear(smartspaceView.id, TOP)
-                        connect(smartspaceView.id, TOP, dateView.id, BOTTOM)
-                    }
-                }
-            }
+            createBarrier(
+                com.android.systemui.res.R.id.smart_space_barrier_bottom,
+                Barrier.BOTTOM,
+                0,
+                *intArrayOf(
+                    R.id.bc_smartspace_view,
+                    R.id.date_smartspace_view,
+                    R.id.weather_smartspace_view,
+                )
+            )
         }
         updateVisibility(constraintSet)
     }
@@ -156,30 +205,28 @@
                 }
             }
         }
+        smartspaceView?.viewTreeObserver?.removeOnGlobalLayoutListener(smartspaceVisibilityListener)
+        smartspaceVisibilityListener = null
     }
 
     private fun updateVisibility(constraintSet: ConstraintSet) {
         constraintSet.apply {
-            weatherView?.let {
-                setVisibility(
-                    it.id,
-                    when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
-                        true -> ConstraintSet.GONE
-                        false ->
-                            when (keyguardSmartspaceViewModel.isWeatherEnabled) {
-                                true -> ConstraintSet.VISIBLE
-                                false -> ConstraintSet.GONE
-                            }
-                    }
-                )
-            }
-            dateView?.let {
-                setVisibility(
-                    it.id,
-                    if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
-                    else ConstraintSet.VISIBLE
-                )
-            }
+            setVisibility(
+                R.id.weather_smartspace_view,
+                when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+                    true -> ConstraintSet.GONE
+                    false ->
+                        when (keyguardSmartspaceViewModel.isWeatherEnabled) {
+                            true -> ConstraintSet.VISIBLE
+                            false -> ConstraintSet.GONE
+                        }
+                }
+            )
+            setVisibility(
+                R.id.date_smartspace_view,
+                if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
+                else ConstraintSet.VISIBLE
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
deleted file mode 100644
index 19ba1aa..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import javax.inject.Inject
-
-class SplitShadeClockSection
-@Inject
-constructor(
-    clockInteractor: KeyguardClockInteractor,
-    keyguardClockViewModel: KeyguardClockViewModel,
-    context: Context,
-    splitShadeStateController: SplitShadeStateController,
-) : ClockSection(clockInteractor, keyguardClockViewModel, context, splitShadeStateController) {
-    override fun applyDefaultConstraints(constraints: ConstraintSet) {
-        super.applyDefaultConstraints(constraints)
-        val largeClockEndGuideline =
-            if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
-            else R.id.split_shade_guideline
-        constraints.apply {
-            connect(
-                R.id.lockscreen_clock_view_large,
-                ConstraintSet.END,
-                largeClockEndGuideline,
-                ConstraintSet.END
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index f20ab06..b12a8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -20,7 +20,6 @@
 import android.view.View
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
-import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -31,11 +30,9 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.media.controls.ui.KeyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
 import javax.inject.Inject
 
 /** Aligns media on left side for split shade, below smartspace, date, and weather. */
@@ -44,11 +41,9 @@
 constructor(
     private val context: Context,
     private val notificationPanelView: NotificationPanelView,
-    private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
     private val keyguardMediaController: KeyguardMediaController
 ) : KeyguardSection() {
     private val mediaContainerId = R.id.status_view_media_container
-    private val smartSpaceBarrier = R.id.smart_space_barrier_bottom
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
@@ -85,18 +80,7 @@
         constraintSet.apply {
             constrainWidth(mediaContainerId, MATCH_CONSTRAINT)
             constrainHeight(mediaContainerId, WRAP_CONTENT)
-
-            createBarrier(
-                smartSpaceBarrier,
-                Barrier.BOTTOM,
-                0,
-                *intArrayOf(
-                    sharedR.id.bc_smartspace_view,
-                    sharedR.id.date_smartspace_view,
-                    sharedR.id.weather_smartspace_view,
-                )
-            )
-            connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM)
+            connect(mediaContainerId, TOP, R.id.smart_space_barrier_bottom, BOTTOM)
             connect(mediaContainerId, START, PARENT_ID, START)
             connect(mediaContainerId, END, R.id.split_shade_guideline, END)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
deleted file mode 100644
index 8728ada..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
-import javax.inject.Inject
-
-/*
- * We need this class for the splitShadeBlueprint so `addViews` and `removeViews` will be called
- * when switching to and from splitShade.
- */
-class SplitShadeSmartspaceSection
-@Inject
-constructor(
-    keyguardClockViewModel: KeyguardClockViewModel,
-    keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
-    context: Context,
-    smartspaceController: LockscreenSmartspaceController,
-    keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
-) :
-    SmartspaceSection(
-        keyguardClockViewModel,
-        keyguardSmartspaceViewModel,
-        context,
-        smartspaceController,
-        keyguardUnlockAnimationController,
-    )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index a1dd720..e8c1ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -33,6 +34,7 @@
     @Application applicationScope: CoroutineScope,
     smartspaceController: LockscreenSmartspaceController,
     keyguardClockViewModel: KeyguardClockViewModel,
+    smartspaceInteractor: KeyguardSmartspaceInteractor,
 ) {
     /** Whether the smartspace section is available in the build. */
     val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
@@ -78,4 +80,7 @@
     ): Boolean {
         return !clockIncludesCustomWeatherDisplay && isWeatherEnabled
     }
+
+    /* trigger clock and smartspace constraints change when smartspace appears */
+    var bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5ee38be..a48fb45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -240,13 +240,13 @@
      */
     val translationY: Flow<Float> =
         combine(
-            isOnLockscreen,
+            isOnLockscreenWithoutShade,
             merge(
                 keyguardInteractor.keyguardTranslationY,
                 occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
             )
-        ) { isOnLockscreen, translationY ->
-            if (isOnLockscreen) {
+        ) { isOnLockscreenWithoutShade, translationY ->
+            if (isOnLockscreenWithoutShade) {
                 translationY
             } else {
                 0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2b90e64..e309c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -265,13 +265,6 @@
             return factory.create("VerboseMobileViewLog", 100)
         }
 
-        @Provides
-        @SysUISingleton
-        @OemSatelliteInputLog
-        fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer {
-            return factory.create("DeviceBasedSatelliteInputLog", 32)
-        }
-
         const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
             "FirstMobileSubShowingNetworkTypeIcon"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
index 8400fb0..e3c3139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
-import com.android.systemui.statusbar.pipeline.satellite.ui.DeviceBasedSatelliteBindableIcon
 import javax.inject.Inject
 
 /**
@@ -39,12 +38,11 @@
 class BindableIconsRegistryImpl
 @Inject
 constructor(
-    /** Bindables go here */
-    oemSatellite: DeviceBasedSatelliteBindableIcon
+/** Bindables go here */
 ) : BindableIconsRegistry {
     /**
      * Adding the injected bindables to this list will get them registered with
      * StatusBarIconController
      */
-    override val bindableIcons: List<BindableIcon> = listOf(oemSatellite)
+    override val bindableIcons: List<BindableIcon> = listOf()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 5e6e36d..de46a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -19,17 +19,11 @@
 import android.os.OutcomeReceiver
 import android.telephony.satellite.NtnSignalStrengthCallback
 import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
 import android.telephony.satellite.SatelliteModemStateCallback
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.core.MessageInitializer
-import com.android.systemui.log.core.MessagePrinter
-import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
@@ -129,7 +123,6 @@
     satelliteManagerOpt: Optional<SatelliteManager>,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
-    @OemSatelliteInputLog private val logBuffer: LogBuffer,
     private val systemClock: SystemClock,
 ) : DeviceBasedSatelliteRepository {
 
@@ -152,11 +145,6 @@
                 ensureMinUptime(systemClock, MIN_UPTIME)
                 satelliteSupport.value = satelliteManager.checkSatelliteSupported()
 
-                logBuffer.i(
-                    { str1 = satelliteSupport.value.toString() },
-                    { "Checked for system support. support=$str1" },
-                )
-
                 // We only need to check location availability if this mode is supported
                 if (satelliteSupport.value is Supported) {
                     isSatelliteAllowedForCurrentLocation.subscriptionCount
@@ -171,9 +159,6 @@
                                  * connection might cause more frequent checks.
                                  */
                                 while (true) {
-                                    logBuffer.i {
-                                        "requestIsSatelliteCommunicationAllowedForCurrentLocation"
-                                    }
                                     checkIsSatelliteAllowed()
                                     delay(POLLING_INTERVAL_MS)
                                 }
@@ -182,8 +167,6 @@
                 }
             }
         } else {
-            logBuffer.i { "Satellite manager is null" }
-
             satelliteSupport.value = NotSupported
         }
     }
@@ -198,21 +181,12 @@
     private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
         conflatedCallbackFlow {
                 val cb = SatelliteModemStateCallback { state ->
-                    logBuffer.i({ int1 = state }) { "onSatelliteModemStateChanged: state=$int1" }
                     trySend(SatelliteConnectionState.fromModemState(state))
                 }
 
-                var registered = false
+                sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
 
-                try {
-                    val res =
-                        sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
-                    registered = res == SATELLITE_RESULT_SUCCESS
-                } catch (e: Exception) {
-                    logBuffer.e("error registering for modem state", e)
-                }
-
-                awaitClose { if (registered) sm.unregisterForSatelliteModemStateChanged(cb) }
+                awaitClose { sm.unregisterForSatelliteModemStateChanged(cb) }
             }
             .flowOn(bgDispatcher)
 
@@ -223,21 +197,12 @@
     private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
         conflatedCallbackFlow {
                 val cb = NtnSignalStrengthCallback { signalStrength ->
-                    logBuffer.i({ int1 = signalStrength.level }) {
-                        "onNtnSignalStrengthChanged: level=$int1"
-                    }
                     trySend(signalStrength.level)
                 }
 
-                var registered = false
-                try {
-                    sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
-                    registered = true
-                } catch (e: Exception) {
-                    logBuffer.e("error registering for signal strength", e)
-                }
+                sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
 
-                awaitClose { if (registered) sm.unregisterForNtnSignalStrengthChanged(cb) }
+                awaitClose { sm.unregisterForNtnSignalStrengthChanged(cb) }
             }
             .flowOn(bgDispatcher)
 
@@ -248,15 +213,11 @@
                 bgDispatcher.asExecutor(),
                 object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
                     override fun onError(e: SatelliteManager.SatelliteException) {
-                        logBuffer.e(
-                            "Found exception when checking availability",
-                            e,
-                        )
+                        android.util.Log.e(TAG, "Found exception when checking for satellite: ", e)
                         isSatelliteAllowedForCurrentLocation.value = false
                     }
 
                     override fun onResult(allowed: Boolean) {
-                        logBuffer.i { allowed.toString() }
                         isSatelliteAllowedForCurrentLocation.value = allowed
                     }
                 }
@@ -278,12 +239,6 @@
                     }
 
                     override fun onError(error: SatelliteManager.SatelliteException) {
-                        logBuffer.e(
-                            "Exception when checking for satellite support. " +
-                                "Assuming it is not supported for this device.",
-                            error,
-                        )
-
                         // Assume that an error means it's not supported
                         continuation.resume(NotSupported)
                     }
@@ -309,19 +264,5 @@
                 delay(timeTilMinUptime)
             }
         }
-
-        /** A couple of convenience logging methods rather than a whole class */
-        private fun LogBuffer.i(
-            initializer: MessageInitializer = {},
-            printer: MessagePrinter,
-        ) = this.log(TAG, LogLevel.INFO, initializer, printer)
-
-        private fun LogBuffer.e(message: String, exception: Throwable? = null) =
-            this.log(
-                tag = TAG,
-                level = LogLevel.ERROR,
-                message = message,
-                exception = exception,
-            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
deleted file mode 100644
index f5d0f6b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui
-
-import android.content.Context
-import com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
-import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
-import com.android.systemui.statusbar.pipeline.satellite.ui.binder.DeviceBasedSatelliteIconBinder
-import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
-import javax.inject.Inject
-
-@SysUISingleton
-class DeviceBasedSatelliteBindableIcon
-@Inject
-constructor(
-    context: Context,
-    viewModel: DeviceBasedSatelliteViewModel,
-) : BindableIcon {
-    override val slot: String =
-        context.getString(com.android.internal.R.string.status_bar_oem_satellite)
-
-    override val initializer = ModernStatusBarViewCreator { context ->
-        SingleBindableStatusBarIconView.createView(context).also { view ->
-            view.initView(slot) { DeviceBasedSatelliteIconBinder.bind(view, viewModel) }
-        }
-    }
-
-    override val shouldBindIcon: Boolean = oemEnabledSatelliteFlag()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
deleted file mode 100644
index 59ac5f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.common.ui.binder.IconViewBinder
-import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
-import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
-import kotlinx.coroutines.launch
-
-object DeviceBasedSatelliteIconBinder {
-    fun bind(
-        view: SingleBindableStatusBarIconView,
-        viewModel: DeviceBasedSatelliteViewModel,
-    ): ModernStatusBarViewBinding {
-        return SingleBindableStatusBarIconView.withDefaultBinding(
-            view = view,
-            shouldBeVisible = { viewModel.icon.value != null }
-        ) {
-            lifecycleScope.launch {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
-                    viewModel.icon.collect { newIcon ->
-                        if (newIcon == null) {
-                            view.iconView.setImageDrawable(null)
-                        } else {
-                            IconViewBinder.bind(newIcon, view.iconView)
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
deleted file mode 100644
index 6938d66..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.model
-
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
-
-/**
- * Define the [Icon] that relates to a given satellite connection state + level. Note that for now
- * We don't need any data class box, so we can just use a simple mapping function.
- */
-object SatelliteIconModel {
-    fun fromConnectionState(
-        connectionState: SatelliteConnectionState,
-        signalStrength: Int,
-    ): Icon? =
-        when (connectionState) {
-            // TODO(b/316635648): check if this should be null
-            SatelliteConnectionState.Unknown,
-            SatelliteConnectionState.Off,
-            SatelliteConnectionState.On ->
-                Icon.Resource(
-                    res = R.drawable.ic_satellite_not_connected,
-                    contentDescription = null,
-                )
-            SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
-        }
-
-    private fun fromSignalStrength(
-        signalStrength: Int,
-    ): Icon? =
-        // TODO(b/316634365): these need content descriptions
-        when (signalStrength) {
-            // No signal
-            0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null)
-
-            // Poor -> Moderate
-            1,
-            2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null)
-
-            // Good -> Great
-            3,
-            4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null)
-            else -> null
-        }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
deleted file mode 100644
index 0051161..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
-
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
-import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * View-Model for the device-based satellite icon. This icon will only show in the status bar if
- * satellite is available AND all other service states are considered OOS.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class DeviceBasedSatelliteViewModel
-@Inject
-constructor(
-    interactor: DeviceBasedSatelliteInteractor,
-    @Application scope: CoroutineScope,
-) {
-    private val shouldShowIcon: StateFlow<Boolean> =
-        interactor.areAllConnectionsOutOfService
-            .flatMapLatest { allOos ->
-                if (!allOos) {
-                    flowOf(false)
-                } else {
-                    interactor.isSatelliteAllowed
-                }
-            }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
-    val icon: StateFlow<Icon?> =
-        combine(
-                shouldShowIcon,
-                interactor.connectionState,
-                interactor.signalStrength,
-            ) { shouldShow, state, signalStrength ->
-                if (shouldShow) {
-                    SatelliteIconModel.fromConnectionState(state, signalStrength)
-                } else {
-                    null
-                }
-            }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index 25a2c9d..3b87bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -103,7 +103,7 @@
      *
      * Creates a dot view, and uses [bindingCreator] to get and set the binding.
      */
-    open fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+    fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
         // The dot view requires [slot] to be set, and the [binding] may require an instantiated dot
         // view. So, this is the required order.
         this.slot = slot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
deleted file mode 100644
index c663c37..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.shared.ui.view
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.graphics.Color
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.ImageView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
-
-/** Simple single-icon view that is bound to bindable_status_bar_icon.xml */
-class SingleBindableStatusBarIconView(
-    context: Context,
-    attrs: AttributeSet?,
-) : ModernStatusBarView(context, attrs) {
-
-    internal lateinit var iconView: ImageView
-    internal lateinit var dotView: StatusBarIconView
-
-    override fun toString(): String {
-        return "SingleBindableStatusBarIcon(" +
-            "slot='$slot', " +
-            "isCollecting=${binding.isCollecting()}, " +
-            "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
-            "viewString=${super.toString()}"
-    }
-
-    override fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
-        super.initView(slot, bindingCreator)
-
-        iconView = requireViewById(R.id.icon_view)
-        dotView = requireViewById(R.id.status_bar_dot)
-    }
-
-    companion object {
-        fun createView(
-            context: Context,
-        ): SingleBindableStatusBarIconView {
-            return LayoutInflater.from(context).inflate(R.layout.bindable_status_bar_icon, null)
-                as SingleBindableStatusBarIconView
-        }
-
-        /**
-         * Using a given binding [block], create the necessary scaffolding to handle the general
-         * case of a single status bar icon. This includes eliding into a dot view when there is not
-         * enough space, and handling tint.
-         *
-         * [block] should be a simple [launch] call that handles updating the single icon view with
-         * its new view. Currently there is no simple way to e.g., extend to handle multiple tints
-         * for dual-layered icons, and any more complex logic should probably find a way to return
-         * its own version of [ModernStatusBarViewBinding].
-         */
-        fun withDefaultBinding(
-            view: SingleBindableStatusBarIconView,
-            shouldBeVisible: () -> Boolean,
-            block: suspend LifecycleOwner.(View) -> Unit
-        ): SingleBindableStatusBarIconViewBinding {
-            @StatusBarIconView.VisibleState
-            val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
-
-            val iconTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
-            val decorTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
-
-            var isCollecting: Boolean = false
-
-            view.repeatWhenAttached {
-                // Child binding
-                block(view)
-
-                lifecycleScope.launch {
-                    repeatOnLifecycle(Lifecycle.State.STARTED) {
-                        // isVisible controls the visibility state of the outer group, and thus it
-                        // needs
-                        // to run in the CREATED lifecycle so it can continue to watch while
-                        // invisible
-                        // See (b/291031862) for details
-                        launch {
-                            visibilityState.collect { visibilityState ->
-                                // for b/296864006, we can not hide all the child views if
-                                // visibilityState is STATE_HIDDEN. Because hiding all child views
-                                // would cause the
-                                // getWidth() of this view return 0, and that would cause the
-                                // translation
-                                // calculation fails in StatusIconContainer. Therefore, like class
-                                // MobileIconBinder, instead of set the child views visibility to
-                                // View.GONE,
-                                // we set their visibility to View.INVISIBLE to make them invisible
-                                // but
-                                // keep the width.
-                                ModernStatusBarViewVisibilityHelper.setVisibilityState(
-                                    visibilityState,
-                                    view.iconView,
-                                    view.dotView,
-                                )
-                            }
-                        }
-
-                        launch {
-                            iconTint.collect { tint ->
-                                val tintList = ColorStateList.valueOf(tint)
-                                view.iconView.imageTintList = tintList
-                                view.dotView.setDecorColor(tint)
-                            }
-                        }
-
-                        launch {
-                            decorTint.collect { decorTint -> view.dotView.setDecorColor(decorTint) }
-                        }
-
-                        try {
-                            awaitCancellation()
-                        } finally {
-                            isCollecting = false
-                        }
-                    }
-                }
-            }
-
-            return object : SingleBindableStatusBarIconViewBinding {
-                override val decorTint: Int
-                    get() = decorTint.value
-
-                override val iconTint: Int
-                    get() = iconTint.value
-
-                override fun getShouldIconBeVisible(): Boolean {
-                    return shouldBeVisible()
-                }
-
-                override fun onVisibilityStateChanged(state: Int) {
-                    visibilityState.value = state
-                }
-
-                override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
-                    iconTint.value = newTint
-                }
-
-                override fun onDecorTintChanged(newTint: Int) {
-                    decorTint.value = newTint
-                }
-
-                override fun isCollecting(): Boolean {
-                    return isCollecting
-                }
-            }
-        }
-    }
-}
-
-@VisibleForTesting
-interface SingleBindableStatusBarIconViewBinding : ModernStatusBarViewBinding {
-    val iconTint: Int
-    val decorTint: Int
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 2afb3a1..d86d123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -640,11 +640,10 @@
     @Test
     public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
             throws RemoteException {
-
         enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        Mockito.reset(mSpyController);
+        resetMockObjects();
         getInstrumentation().runOnMainSync(() -> {
             mWindowMagnificationAnimationController.deleteWindowMagnification(
                     mAnimationCallback2);
@@ -658,6 +657,11 @@
             mValueAnimator.end();
         });
 
+        // wait for animation returns
+        waitForIdleSync();
+
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} will only
+        // be triggered once in {@link ValueAnimator#end()}
         verify(mSpyController).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -717,7 +721,11 @@
         deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        deleteWindowMagnificationAndWaitAnimating(0, null);
+        // Verifying that WindowMagnificationController#deleteWindowMagnification is never called
+        // in previous steps
+        verify(mSpyController, never()).deleteWindowMagnification();
+
+        deleteWindowMagnificationWithoutAnimation();
 
         verify(mSpyController).deleteWindowMagnification();
         verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
@@ -810,6 +818,8 @@
             mWindowMagnificationAnimationController.enableWindowMagnification(
                     targetScale, targetCenterX, targetCenterY, null);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void enableWindowMagnificationAndWaitAnimating(long duration,
@@ -829,12 +839,16 @@
                     targetScale, targetCenterX, targetCenterY, callback);
             advanceTimeBy(duration);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void deleteWindowMagnificationWithoutAnimation() {
         getInstrumentation().runOnMainSync(() -> {
             mWindowMagnificationAnimationController.deleteWindowMagnification(null);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void deleteWindowMagnificationAndWaitAnimating(long duration,
@@ -843,6 +857,8 @@
             mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
             advanceTimeBy(duration);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index bd4973d..a57b890 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,6 +1,6 @@
 package com.android.systemui.biometrics.domain.model
 
-import android.hardware.biometrics.PromptContentListItemBulletedText
+import android.hardware.biometrics.PromptContentItemBulletedText
 import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -28,7 +28,8 @@
         val contentView =
             PromptVerticalListContentView.Builder()
                 .setDescription("content description")
-                .addListItem(PromptContentListItemBulletedText("content text"))
+                .addListItem(PromptContentItemBulletedText("content item 1"))
+                .addListItem(PromptContentItemBulletedText("content item 2"), 1)
                 .build()
 
         val fpPros = fingerprintSensorPropertiesInternal().first()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index bf61c2e..3944054 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -18,7 +18,7 @@
 
 import android.content.res.Configuration
 import android.graphics.Point
-import android.hardware.biometrics.PromptContentListItemBulletedText
+import android.hardware.biometrics.PromptContentItemBulletedText
 import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptInfo
 import android.hardware.biometrics.PromptVerticalListContentView
@@ -140,7 +140,8 @@
         selector.resetPrompt()
         promptContentView =
             PromptVerticalListContentView.Builder()
-                .addListItem(PromptContentListItemBulletedText("test"))
+                .addListItem(PromptContentItemBulletedText("content item 1"))
+                .addListItem(PromptContentItemBulletedText("content item 2"), 1)
                 .build()
 
         viewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index a4d217f..5dd37ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -21,13 +21,17 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.util.mockito.whenever
 import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -48,32 +52,60 @@
     @Mock private lateinit var smallClockView: View
     @Mock private lateinit var smallClockFaceLayout: ClockFaceLayout
     @Mock private lateinit var largeClockFaceLayout: ClockFaceLayout
+    @Mock private lateinit var clockViewModel: KeyguardClockViewModel
+    private val clockSize = MutableStateFlow(LARGE)
+    private val currentClock: MutableStateFlow<ClockController?> = MutableStateFlow(null)
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-    }
-
-    @Test
-    fun addClockViews_nonWeatherClock() {
-        setupNonWeatherClock()
-        KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
-        verify(rootView).addView(smallClockView)
-        verify(rootView).addView(largeClockView)
-        verify(burnInLayer).addView(smallClockView)
-        verify(burnInLayer, never()).addView(largeClockView)
+        whenever(clockViewModel.clockSize).thenReturn(clockSize)
+        whenever(clockViewModel.currentClock).thenReturn(currentClock)
+        whenever(clockViewModel.burnInLayer).thenReturn(burnInLayer)
     }
 
     @Test
     fun addClockViews_WeatherClock() {
         setupWeatherClock()
-        KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
+        KeyguardClockViewBinder.addClockViews(clock, rootView)
         verify(rootView).addView(smallClockView)
         verify(rootView).addView(largeClockView)
-        verify(burnInLayer).addView(smallClockView)
+    }
+
+    @Test
+    fun addClockViews_nonWeatherClock() {
+        setupNonWeatherClock()
+        KeyguardClockViewBinder.addClockViews(clock, rootView)
+        verify(rootView).addView(smallClockView)
+        verify(rootView).addView(largeClockView)
+    }
+    @Test
+    fun addClockViewsToBurnInLayer_LargeWeatherClock() {
+        setupWeatherClock()
+        clockSize.value = LARGE
+        KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+        verify(burnInLayer).removeView(smallClockView)
         verify(burnInLayer).addView(largeClockView)
     }
 
+    @Test
+    fun addClockViewsToBurnInLayer_LargeNonWeatherClock() {
+        setupNonWeatherClock()
+        clockSize.value = LARGE
+        KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+        verify(burnInLayer).removeView(smallClockView)
+        verify(burnInLayer, never()).addView(largeClockView)
+    }
+
+    @Test
+    fun addClockViewsToBurnInLayer_SmallClock() {
+        setupNonWeatherClock()
+        clockSize.value = SMALL
+        KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+        verify(burnInLayer).addView(smallClockView)
+        verify(burnInLayer).removeView(largeClockView)
+    }
+
     private fun setupWeatherClock() {
         setupClock()
         val clockConfig =
@@ -99,5 +131,7 @@
         whenever(clock.smallClock).thenReturn(smallClock)
         whenever(largeClock.layout).thenReturn(largeClockFaceLayout)
         whenever(smallClock.layout).thenReturn(smallClockFaceLayout)
+        whenever(clockViewModel.clock).thenReturn(clock)
+        currentClock.value = clock
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 070a0cc..57b5559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -22,6 +22,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.res.R
@@ -31,6 +32,8 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,6 +49,8 @@
     @Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
     @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
     @Mock private lateinit var splitShadeStateController: SplitShadeStateController
+    @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
+    private val clockShouldBeCentered: MutableStateFlow<Boolean> = MutableStateFlow(true)
 
     private lateinit var underTest: ClockSection
 
@@ -104,12 +109,15 @@
         whenever(packageManager.getResourcesForApplication(anyString())).thenReturn(remoteResources)
         mContext.setMockPackageManager(packageManager)
 
+        whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
+
         underTest =
             ClockSection(
                 keyguardClockInteractor,
                 keyguardClockViewModel,
                 mContext,
                 splitShadeStateController,
+                blueprintInteractor
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 28da957..deb3a83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
@@ -34,7 +36,8 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.StateFlow
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -50,7 +53,8 @@
     @Mock private lateinit var keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel
     @Mock private lateinit var lockscreenSmartspaceController: LockscreenSmartspaceController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
-    @Mock private lateinit var hasCustomWeatherDataDisplay: StateFlow<Boolean>
+    @Mock private lateinit var keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor
+    @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
 
     private val smartspaceView = View(mContext).also { it.id = sharedR.id.bc_smartspace_view }
     private val weatherView = View(mContext).also { it.id = sharedR.id.weather_smartspace_view }
@@ -58,17 +62,22 @@
     private lateinit var constraintLayout: ConstraintLayout
     private lateinit var constraintSet: ConstraintSet
 
+    private val clockShouldBeCentered = MutableStateFlow(false)
+    private val hasCustomWeatherDataDisplay = MutableStateFlow(false)
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         underTest =
             SmartspaceSection(
+                mContext,
                 keyguardClockViewModel,
                 keyguardSmartspaceViewModel,
-                mContext,
+                keyguardSmartspaceInteractor,
                 lockscreenSmartspaceController,
                 keyguardUnlockAnimationController,
+                blueprintInteractor
             )
         constraintLayout = ConstraintLayout(mContext)
         whenever(lockscreenSmartspaceController.buildAndConnectView(any()))
@@ -78,6 +87,7 @@
         whenever(lockscreenSmartspaceController.buildAndConnectDateView(any())).thenReturn(dateView)
         whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
             .thenReturn(hasCustomWeatherDataDisplay)
+        whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
         constraintSet = ConstraintSet()
     }
 
@@ -115,7 +125,7 @@
     fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
         whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
         whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+        hasCustomWeatherDataDisplay.value = false
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
         assertWeatherSmartspaceConstrains(constraintSet)
@@ -129,7 +139,7 @@
 
     @Test
     fun testConstraintsWhenHasCustomWeatherDataDisplay() {
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+        hasCustomWeatherDataDisplay.value = true
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
         assertWeatherSmartspaceConstrains(constraintSet)
@@ -140,7 +150,7 @@
 
     @Test
     fun testNormalDateWeatherVisibility() {
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+        hasCustomWeatherDataDisplay.value = false
         whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(true)
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
@@ -153,7 +163,7 @@
     }
     @Test
     fun testCustomDateWeatherVisibility() {
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+        hasCustomWeatherDataDisplay.value = true
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
index 45f0a8c..44c411f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -66,6 +66,11 @@
 
         private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
         private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
-        private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+        private val DEFAULT_INFO =
+            MediaProjectionInfo(
+                DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_HANDLE,
+                /* launchCookie = */ null
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 9f15b05..a824bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -484,6 +484,46 @@
         }
 
     @Test
+    fun translationYUpdatesOnKeyguard() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY)
+
+            configurationRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                -100
+            )
+            configurationRepository.onAnyConfigurationChange()
+
+            // legacy expansion means the user is swiping up, usually for the bouncer
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+
+            showLockscreen()
+
+            // The translation values are negative
+            assertThat(translationY).isLessThan(0f)
+        }
+
+    @Test
+    fun translationYDoesNotUpdateWhenShadeIsExpanded() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY)
+
+            configurationRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                -100
+            )
+            configurationRepository.onAnyConfigurationChange()
+
+            // legacy expansion means the user is swiping up, usually for the bouncer but also for
+            // shade collapsing
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+
+            showLockscreenWithShadeExpanded()
+
+            assertThat(translationY).isEqualTo(0f)
+        }
+
+    @Test
     fun updateBounds_fromKeyguardRoot() =
         testScope.runTest {
             val bounds by collectLastValue(underTest.bounds)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 7d91e8b..02e6fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -35,7 +35,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -88,7 +87,6 @@
                     Optional.empty(),
                     dispatcher,
                     testScope.backgroundScope,
-                    FakeLogBuffer.Factory.create(),
                     systemClock,
                 )
 
@@ -102,22 +100,6 @@
         }
 
     @Test
-    fun satelliteManagerThrows_doesNotCrash() =
-        testScope.runTest {
-            setupDefaultRepo()
-
-            whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any()))
-                .thenThrow(SatelliteException(13))
-
-            val conn by collectLastValue(underTest.connectionState)
-            val strength by collectLastValue(underTest.signalStrength)
-
-            // Flows have not emitted, we haven't crashed
-            assertThat(conn).isNull()
-            assertThat(strength).isNull()
-        }
-
-    @Test
     fun connectionState_mapsFromSatelliteModemState() =
         testScope.runTest {
             setupDefaultRepo()
@@ -398,7 +380,6 @@
                 if (satMan != null) Optional.of(satMan) else Optional.empty(),
                 dispatcher,
                 testScope.backgroundScope,
-                FakeLogBuffer.Factory.create(),
                 systemClock,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
deleted file mode 100644
index 21c038a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
-import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
-    private lateinit var underTest: DeviceBasedSatelliteViewModel
-    private lateinit var interactor: DeviceBasedSatelliteInteractor
-
-    private val repo = FakeDeviceBasedSatelliteRepository()
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-
-    private val testScope = TestScope()
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        interactor =
-            DeviceBasedSatelliteInteractor(
-                repo,
-                mobileIconsInteractor,
-                testScope.backgroundScope,
-            )
-
-        underTest =
-            DeviceBasedSatelliteViewModel(
-                interactor,
-                testScope.backgroundScope,
-            )
-    }
-
-    @Test
-    fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.icon)
-
-            // GIVEN satellite is not allowed
-            repo.isSatelliteAllowedForCurrentLocation.value = false
-
-            // GIVEN all icons are OOS
-            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
-            i1.isInService.value = false
-
-            // THEN icon is null because we should not be showing it
-            assertThat(latest).isNull()
-        }
-
-    @Test
-    fun icon_nullWhenShouldNotShow_notAllOos() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.icon)
-
-            // GIVEN satellite is allowed
-            repo.isSatelliteAllowedForCurrentLocation.value = true
-
-            // GIVEN all icons are not OOS
-            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
-            i1.isInService.value = true
-
-            // THEN icon is null because we have service
-            assertThat(latest).isNull()
-        }
-
-    @Test
-    fun icon_satelliteIsOff() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.icon)
-
-            // GIVEN satellite is allowed
-            repo.isSatelliteAllowedForCurrentLocation.value = true
-
-            // GIVEN all icons are OOS
-            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
-            i1.isInService.value = false
-
-            // THEN icon is null because we have service
-            assertThat(latest).isInstanceOf(Icon::class.java)
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
deleted file mode 100644
index ca9df57..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.shared.ui.view
-
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.StatusBarIconView
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * Being a simple subclass of [ModernStatusBarView], use the same basic test cases to verify the
- * root behavior, and add testing for the new [SingleBindableStatusBarIconView.withDefaultBinding]
- * method.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class SingleBindableStatusBarIconViewTest : SysuiTestCase() {
-    private lateinit var binding: SingleBindableStatusBarIconViewBinding
-
-    // Visibility is outsourced to view-models. This simulates it
-    private var isVisible = true
-    private var visibilityFn: () -> Boolean = { isVisible }
-
-    @Test
-    fun initView_hasCorrectSlot() {
-        val view = createAndInitView()
-
-        assertThat(view.slot).isEqualTo(SLOT_NAME)
-    }
-
-    @Test
-    fun getVisibleState_icon_returnsIcon() {
-        val view = createAndInitView()
-
-        view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
-
-        assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_ICON)
-    }
-
-    @Test
-    fun getVisibleState_dot_returnsDot() {
-        val view = createAndInitView()
-
-        view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
-
-        assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_DOT)
-    }
-
-    @Test
-    fun getVisibleState_hidden_returnsHidden() {
-        val view = createAndInitView()
-
-        view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
-
-        assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_HIDDEN)
-    }
-
-    @Test
-    fun onDarkChanged_bindingReceivesIconAndDecorTint() {
-        val view = createAndInitView()
-
-        view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
-
-        assertThat(binding.iconTint).isEqualTo(0x12345678)
-        assertThat(binding.decorTint).isEqualTo(0x12345678)
-    }
-
-    @Test
-    fun setStaticDrawableColor_bindingReceivesIconTint() {
-        val view = createAndInitView()
-
-        view.setStaticDrawableColor(0x12345678, 0x12344321)
-
-        assertThat(binding.iconTint).isEqualTo(0x12345678)
-    }
-
-    @Test
-    fun setDecorColor_bindingReceivesDecorColor() {
-        val view = createAndInitView()
-
-        view.setDecorColor(0x23456789)
-
-        assertThat(binding.decorTint).isEqualTo(0x23456789)
-    }
-
-    @Test
-    fun isIconVisible_usesBinding_true() {
-        val view = createAndInitView()
-
-        isVisible = true
-
-        assertThat(view.isIconVisible).isEqualTo(true)
-    }
-
-    @Test
-    fun isIconVisible_usesBinding_false() {
-        val view = createAndInitView()
-
-        isVisible = false
-
-        assertThat(view.isIconVisible).isEqualTo(false)
-    }
-
-    @Test
-    fun getDrawingRect_takesTranslationIntoAccount() {
-        val view = createAndInitView()
-
-        view.translationX = 50f
-        view.translationY = 60f
-
-        val drawingRect = Rect()
-        view.getDrawingRect(drawingRect)
-
-        assertThat(drawingRect.left).isEqualTo(view.left + 50)
-        assertThat(drawingRect.right).isEqualTo(view.right + 50)
-        assertThat(drawingRect.top).isEqualTo(view.top + 60)
-        assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
-    }
-
-    private fun createAndInitView(): SingleBindableStatusBarIconView {
-        val view = SingleBindableStatusBarIconView.createView(context)
-        binding = SingleBindableStatusBarIconView.withDefaultBinding(view, visibilityFn) {}
-        view.initView(SLOT_NAME) { binding }
-        return view
-    }
-
-    companion object {
-        private const val SLOT_NAME = "test_slot"
-    }
-}
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index a0301a9..6e906eb 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -34,7 +34,7 @@
 
     private volatile boolean mShouldProcessRequests = false;
 
-    private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
+    private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500);
 
     SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
         super(associationId, fd, context);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index eb6fdd7..f921b0b 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -418,6 +418,8 @@
             LinkCapacityEstimate.INVALID, LinkCapacityEstimate.INVALID)));
     private List<List<LinkCapacityEstimate>> mLinkCapacityEstimateLists;
 
+    private int[] mSimultaneousCellularCallingSubIds = {};
+
     private int[] mECBMReason;
     private boolean[] mECBMStarted;
     private int[] mSCBMReason;
@@ -564,7 +566,9 @@
                 || events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
                 || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
                 || events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)
-                || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
+                || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)
+                || events.contains(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
     }
 
     private static final int MSG_USER_SWITCHED = 1;
@@ -1427,6 +1431,15 @@
                         remove(r.binder);
                     }
                 }
+                if (events.contains(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+                    try {
+                        r.callback.onSimultaneousCallingStateChanged(
+                                mSimultaneousCellularCallingSubIds);
+                    } catch (RemoteException ex) {
+                        remove(r.binder);
+                    }
+                }
                 if (events.contains(
                         TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) {
                     try {
@@ -3092,6 +3105,43 @@
         }
     }
 
+    /**
+     * Notify the listeners that simultaneous cellular calling subscriptions have changed
+     * @param subIds The set of subIds that support simultaneous cellular calling
+     */
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(int[] subIds) {
+        if (!checkNotifyPermission("notifySimultaneousCellularCallingSubscriptionsChanged()")) {
+            return;
+        }
+
+        if (VDBG) {
+            StringBuilder b = new StringBuilder();
+            b.append("notifySimultaneousCellularCallingSubscriptionsChanged: ");
+            b.append("subIds = {");
+            for (int i : subIds) {
+                b.append(" ");
+                b.append(i);
+            }
+            b.append("}");
+            log(b.toString());
+        }
+
+        synchronized (mRecords) {
+            mSimultaneousCellularCallingSubIds = subIds;
+            for (Record r : mRecords) {
+                if (r.matchTelephonyCallbackEvent(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+                    try {
+                        r.callback.onSimultaneousCallingStateChanged(subIds);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @Override
     public void addCarrierPrivilegesCallback(
             int phoneId,
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index fd17261..c18bacb 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -172,6 +172,7 @@
     public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
             "android.hardware.audio.core.IModule/",
             "android.hardware.audio.core.IConfig/",
+            "android.hardware.audio.effect.IFactory/",
             "android.hardware.biometrics.face.IFace/",
             "android.hardware.biometrics.fingerprint.IFingerprint/",
             "android.hardware.bluetooth.IBluetoothHci/",
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b03183c..fa5dbd2 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2935,7 +2935,11 @@
         return true;
     }
 
-    private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+    private static boolean unfreezePackageCgroup(int packageUID) {
+        return freezePackageCgroup(packageUID, false);
+    }
+
+    private static void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
                                                      int packageUID) {
         // Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
         // Since we're going to kill these, we don't need to unfreze them later.
@@ -2943,12 +2947,9 @@
         // processes (forks) should not be Binder users.
         int N = procs.size();
         for (int i = 0; i < N; i++) {
-            final int uid = procs.get(i).first.uid;
             final int pid = procs.get(i).first.getPid();
             int nRetries = 0;
-            // We only freeze the cgroup of the target package, so we do not need to freeze the
-            // Binder interfaces of dependant processes in other UIDs.
-            if (pid > 0 && uid == packageUID) {
+            if (pid > 0) {
                 try {
                     int rc;
                     do {
@@ -2962,12 +2963,19 @@
         }
 
         // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
-        // despite being added to a new child cgroup. The cgroups of package dependant processes are
-        // not frozen, since it's possible this would freeze processes with no dependency on the
-        // package being killed here.
+        // despite being added to a child cgroup created after this call that would otherwise be
+        // unfrozen.
         freezePackageCgroup(packageUID, true);
     }
 
+    private static List<Pair<ProcessRecord, Boolean>> getUIDSublist(
+            List<Pair<ProcessRecord, Boolean>> procs, int startIdx) {
+        final int uid = procs.get(startIdx).first.uid;
+        int endIdx = startIdx + 1;
+        while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx;
+        return procs.subList(startIdx, endIdx);
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     boolean killPackageProcessesLSP(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -3063,25 +3071,36 @@
             }
         }
 
-        final int packageUID = UserHandle.getUid(userId, appId);
-        final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
-                              && appId <= Process.LAST_APPLICATION_UID;
-        if (doFreeze) {
-            freezeBinderAndPackageCgroup(procs, packageUID);
+        final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID
+                                    && appId <= Process.LAST_APPLICATION_UID;
+
+        if (killingUserApp) {
+            procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid));
         }
 
-        int N = procs.size();
-        for (int i=0; i<N; i++) {
-            final Pair<ProcessRecord, Boolean> proc = procs.get(i);
-            removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
-                    reasonCode, subReason, reason, !doFreeze /* async */);
+        int idx = 0;
+        while (idx < procs.size()) {
+            final List<Pair<ProcessRecord, Boolean>> uidProcs = getUIDSublist(procs, idx);
+            final int packageUID = uidProcs.get(0).first.uid;
+
+            // Do not freeze for system apps or for dependencies of the targeted package, but
+            // make sure to freeze the targeted package for all users if called with USER_ALL.
+            final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId;
+
+            if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID);
+
+            for (Pair<ProcessRecord, Boolean> proc : uidProcs) {
+                removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
+                        reasonCode, subReason, reason, !doFreeze /* async */);
+            }
+            killAppZygotesLocked(packageName, appId, userId, false /* force */);
+
+            if (doFreeze) unfreezePackageCgroup(packageUID);
+
+            idx += uidProcs.size();
         }
-        killAppZygotesLocked(packageName, appId, userId, false /* force */);
         mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
-        if (doFreeze) {
-            freezePackageCgroup(packageUID, false);
-        }
-        return N > 0;
+        return procs.size() > 0;
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 5189017..b084cf3 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -251,6 +251,7 @@
                             + type);
                 }
             }
+            str.close();
         } catch (XmlPullParserException | java.io.IOException e) {
             Slog.wtf(TAG, "Error reading game manager settings", e);
             return false;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index f80228a..99b45ec 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1812,22 +1812,21 @@
                                         "msg: MSG_L_SET_BT_ACTIVE_DEVICE "
                                             + "received with null profile proxy: "
                                             + btInfo)).printLog(TAG));
-                                sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, 0 /*delay*/);
-                                return;
-                            }
-                            @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
-                                    mBtHelper.getCodecWithFallback(btInfo.mDevice,
-                                            btInfo.mProfile, btInfo.mIsLeOutput,
-                                            "MSG_L_SET_BT_ACTIVE_DEVICE");
-                            mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
-                                    (btInfo.mProfile
-                                            != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
-                                            ? mAudioService.getBluetoothContextualVolumeStream()
-                                            : AudioSystem.STREAM_DEFAULT);
-                            if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
-                                    || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
-                                onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
-                                        "setBluetoothActiveDevice");
+                            } else {
+                                @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+                                        mBtHelper.getCodecWithFallback(btInfo.mDevice,
+                                                btInfo.mProfile, btInfo.mIsLeOutput,
+                                                "MSG_L_SET_BT_ACTIVE_DEVICE");
+                                mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
+                                        (btInfo.mProfile
+                                                != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+                                                ? mAudioService.getBluetoothContextualVolumeStream()
+                                                : AudioSystem.STREAM_DEFAULT);
+                                if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
+                                        || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
+                                    onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+                                            "setBluetoothActiveDevice");
+                                }
                             }
                         }
                     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index bf20ae3..57b19cd 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -764,7 +764,7 @@
     /** only public for mocking/spying, do not call outside of AudioService */
     // @GuardedBy("mDeviceBroker.mSetModeLock")
     @VisibleForTesting
-    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    //@GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     public void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
                                     @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
                                     int streamType) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 1cd267d..d34661d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1290,15 +1290,19 @@
             mService.getHdmiCecNetwork().removeCecSwitches(portId);
         }
 
-        // Turning System Audio Mode off when the AVR is unlugged or standby.
-        // When the device is not unplugged but reawaken from standby, we check if the System
-        // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
-        if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) {
-            HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
-            if (!connected) {
-                setSystemAudioMode(false);
-            } else {
-                onNewAvrAdded(getAvrDeviceInfo());
+        if (!mService.isEarcEnabled() || !mService.isEarcSupported()) {
+            HdmiDeviceInfo avr = getAvrDeviceInfo();
+            if (avr != null
+                    && portId == avr.getPortId()
+                    && isConnectedToArcPort(avr.getPhysicalAddress())) {
+                HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
+                if (connected) {
+                    if (mArcEstablished) {
+                        enableAudioReturnChannel(true);
+                    }
+                } else {
+                    enableAudioReturnChannel(false);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index eaf754d..e0e825d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3617,7 +3617,7 @@
         }
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected boolean isEarcSupported() {
         synchronized (mLock) {
             return mEarcSupported;
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index f3532e5..b6c0e5d 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -53,6 +53,7 @@
     private int mVendorId;
     private String mDisplayName;
     private int mTimeoutRetry;
+    private HdmiDeviceInfo mOldDeviceInfo;
 
     /**
      * Constructor.
@@ -73,6 +74,38 @@
 
     @Override
     public boolean start() {
+        mOldDeviceInfo =
+            localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(mDeviceLogicalAddress);
+        // If there's deviceInfo with same (logical address, physical address) set
+        // Then addCecDevice should be delayed until system information process is finished
+        if (mOldDeviceInfo != null
+                && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress) {
+            Slog.d(TAG, "Start NewDeviceAction with old deviceInfo:["
+                    + mOldDeviceInfo.toString() + "]");
+        } else {
+            // Add the device ahead with default information to handle <Active Source>
+            // promptly, rather than waiting till the new device action is finished.
+            Slog.d(TAG, "Start NewDeviceAction with default deviceInfo");
+            HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder()
+                    .setLogicalAddress(mDeviceLogicalAddress)
+                    .setPhysicalAddress(mDevicePhysicalAddress)
+                    .setPortId(tv().getPortId(mDevicePhysicalAddress))
+                    .setDeviceType(mDeviceType)
+                    .setVendorId(Constants.VENDOR_ID_UNKNOWN)
+                    .build();
+            // If a deviceInfo with same logical address but different physical address exists
+            // We should remove the old deviceInfo first
+            // This will happen if the interval between unplugging and plugging device is too short
+            // and HotplugDetection Action fails to remove the old deviceInfo, or when the newly
+            // plugged device violates HDMI Spec and uses an occupied logical address
+            if (mOldDeviceInfo != null) {
+                Slog.d(TAG, "Remove device by NewDeviceAction, logical address conflicts: "
+                        + mDevicePhysicalAddress);
+                localDevice().mService.getHdmiCecNetwork().removeCecDevice(
+                        localDevice(), mDeviceLogicalAddress);
+            }
+            localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
+        }
         requestOsdName(true);
         return true;
     }
@@ -182,14 +215,30 @@
                 .setVendorId(mVendorId)
                 .setDisplayName(mDisplayName)
                 .build();
-        localDevice().mService.getHdmiCecNetwork().updateCecDevice(deviceInfo);
 
-        // Consume CEC messages we already got for this newly found device.
-        tv().processDelayedMessages(mDeviceLogicalAddress);
+        // Check if oldDevice is same as newDevice
+        // If so, don't add newDevice info, preventing ARC or HDMI source re-connection
+        if (mOldDeviceInfo != null
+                && mOldDeviceInfo.getLogicalAddress() == mDeviceLogicalAddress
+                && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress
+                && mOldDeviceInfo.getDeviceType() == mDeviceType
+                && mOldDeviceInfo.getVendorId() == mVendorId
+                && mOldDeviceInfo.getDisplayName().equals(mDisplayName)) {
+            // Consume CEC messages we already got for this newly found device.
+            tv().processDelayedMessages(mDeviceLogicalAddress);
+            Slog.d(TAG, "Ignore NewDevice, deviceInfo is same as current device");
+            Slog.d(TAG, "Old:[" + mOldDeviceInfo.toString()
+                    + "]; New:[" + deviceInfo.toString() + "]");
+        } else {
+            Slog.d(TAG, "Add NewDevice:[" + deviceInfo.toString() + "]");
+            localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
 
-        if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
-                mDeviceLogicalAddress)) {
-            tv().onNewAvrAdded(deviceInfo);
+            // Consume CEC messages we already got for this newly found device.
+            tv().processDelayedMessages(mDeviceLogicalAddress);
+            if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
+                        mDeviceLogicalAddress)) {
+                tv().onNewAvrAdded(deviceInfo);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index f6571d9..550aed5 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -304,7 +304,7 @@
     }
 
     @VisibleForTesting
-    void addCallback(final IMediaProjectionWatcherCallback callback) {
+    MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
@@ -314,6 +314,7 @@
         synchronized (mLock) {
             mCallbackDelegate.add(callback);
             linkDeathRecipientLocked(callback, deathRecipient);
+            return mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null;
         }
     }
 
@@ -786,11 +787,11 @@
 
         @Override //Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public void addCallback(final IMediaProjectionWatcherCallback callback) {
+        public MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
             addCallback_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaProjectionManagerService.this.addCallback(callback);
+                return MediaProjectionManagerService.this.addCallback(callback);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1244,7 +1245,7 @@
         }
 
         public MediaProjectionInfo getProjectionInfo() {
-            return new MediaProjectionInfo(packageName, userHandle);
+            return new MediaProjectionInfo(packageName, userHandle, mLaunchCookie);
         }
 
         boolean requiresForegroundService() {
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 7f0aadc..c110fb6 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -75,11 +75,9 @@
     private static final int MSG_PACKAGE_ADDED = 1;
     private static final int MSG_PACKAGE_REMOVED = 2;
 
-    private final Context mContext;
     private final BinderService mBinderService;
     private final PackageManager mPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
-    private final UsageStatsManagerInternal mUsageStatsManagerInternal;
     private final PermissionManagerServiceInternal mPermissionManager;
     private final Handler mHandler;
     private final File mDiskFile;
@@ -99,14 +97,14 @@
     @VisibleForTesting
     BackgroundInstallControlService(@NonNull Injector injector) {
         super(injector.getContext());
-        mContext = injector.getContext();
         mPackageManager = injector.getPackageManager();
         mPackageManagerInternal = injector.getPackageManagerInternal();
         mPermissionManager = injector.getPermissionManager();
         mHandler = new EventHandler(injector.getLooper(), this);
         mDiskFile = injector.getDiskFile();
-        mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
-        mUsageStatsManagerInternal.registerListener(
+        UsageStatsManagerInternal usageStatsManagerInternal =
+                injector.getUsageStatsManagerInternal();
+        usageStatsManagerInternal.registerListener(
                 (userId, event) ->
                         mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
                                 userId,
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 888e1c2..c25cea6 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -47,7 +47,6 @@
 public class DataLoaderManagerService extends SystemService {
     private static final String TAG = "DataLoaderManager";
     private final Context mContext;
-    private final HandlerThread mThread;
     private final Handler mHandler;
     private final DataLoaderManagerBinderService mBinderService;
     private final SparseArray<DataLoaderServiceConnection> mServiceConnections =
@@ -57,10 +56,10 @@
         super(context);
         mContext = context;
 
-        mThread = new HandlerThread(TAG);
-        mThread.start();
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
 
-        mHandler = new Handler(mThread.getLooper());
+        mHandler = new Handler(thread.getLooper());
 
         mBinderService = new DataLoaderManagerBinderService();
     }
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index e3bbd2d..f987d4a 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -107,9 +107,6 @@
     @Nullable
     private final ComponentName mInstantAppResolverSettingsComponent;
 
-    @NonNull
-    private final String mRequiredSupplementalProcessPackage;
-
     @Nullable
     private final String mServicesExtensionPackageName;
 
@@ -125,7 +122,6 @@
             @NonNull PackageInstallerService installerService,
             @NonNull PackageProperty packageProperty, @NonNull ComponentName resolveComponentName,
             @Nullable ComponentName instantAppResolverSettingsComponent,
-            @NonNull String requiredSupplementalProcessPackage,
             @Nullable String servicesExtensionPackageName,
             @Nullable String sharedSystemSharedLibraryPackageName) {
         mService = service;
@@ -140,7 +136,6 @@
         mPackageProperty = packageProperty;
         mResolveComponentName = resolveComponentName;
         mInstantAppResolverSettingsComponent = instantAppResolverSettingsComponent;
-        mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
         mServicesExtensionPackageName = servicesExtensionPackageName;
         mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
     }
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 3e5759a..b18f2bf 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -51,6 +51,7 @@
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ArchivedActivityParcel;
+import android.content.pm.ArchivedPackageInfo;
 import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
@@ -402,23 +403,30 @@
                 installerPackage, /* flags= */ 0, userId);
         if (installerInfo == null) {
             // Should never happen because we just fetched the installerInfo.
-            Slog.e(TAG, "Couldnt find installer " + installerPackage);
+            Slog.e(TAG, "Couldn't find installer " + installerPackage);
             return null;
         }
+        final int iconSize = mContext.getSystemService(
+                ActivityManager.class).getLauncherLargeIconSize();
+
+        var info = new ArchivedPackageInfo(archivedPackage);
 
         try {
-            var packageName = archivedPackage.packageName;
-            var mainActivities = archivedPackage.archivedActivities;
-            List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length);
-            for (int i = 0, size = mainActivities.length; i < size; ++i) {
-                var mainActivity = mainActivities[i];
-                Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
+            var packageName = info.getPackageName();
+            var mainActivities = info.getLauncherActivities();
+            List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
+            for (int i = 0, size = mainActivities.size(); i < size; ++i) {
+                var mainActivity = mainActivities.get(i);
+                Path iconPath = storeDrawable(
+                        packageName, mainActivity.getIcon(), userId, i, iconSize);
+                Path monochromePath =  storeDrawable(
+                        packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize);
                 ArchiveActivityInfo activityInfo =
                         new ArchiveActivityInfo(
-                                mainActivity.title,
-                                mainActivity.originalComponentName,
+                                mainActivity.getLabel().toString(),
+                                mainActivity.getComponentName(),
                                 iconPath,
-                                null);
+                                monochromePath);
                 archiveActivityInfos.add(activityInfo);
             }
 
@@ -452,21 +460,6 @@
         return new ArchiveState(archiveActivityInfos, installerTitle);
     }
 
-    // TODO(b/298452477) Handle monochrome icons.
-    private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity,
-            @UserIdInt int userId, int index) throws IOException {
-        if (mainActivity.iconBitmap == null) {
-            return null;
-        }
-        File iconsDir = createIconsDir(packageName, userId);
-        File iconFile = new File(iconsDir, index + ".png");
-        try (FileOutputStream out = new FileOutputStream(iconFile)) {
-            out.write(mainActivity.iconBitmap);
-            out.flush();
-        }
-        return iconFile.toPath();
-    }
-
     @VisibleForTesting
     Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
             @UserIdInt int userId, int index, int iconSize) throws IOException {
@@ -475,9 +468,18 @@
             // The app doesn't define an icon. No need to store anything.
             return null;
         }
+        return storeDrawable(packageName, mainActivity.getIcon(/* density= */ 0), userId, index,
+                iconSize);
+    }
+
+    private static Path storeDrawable(String packageName, @Nullable Drawable iconDrawable,
+            @UserIdInt int userId, int index, int iconSize) throws IOException {
+        if (iconDrawable == null) {
+            return null;
+        }
         File iconsDir = createIconsDir(packageName, userId);
         File iconFile = new File(iconsDir, index + ".png");
-        Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize);
+        Bitmap icon = drawableToBitmap(iconDrawable, iconSize);
         try (FileOutputStream out = new FileOutputStream(iconFile)) {
             // Note: Quality is ignored for PNGs.
             if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5225529..c5b5a76 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4659,8 +4659,7 @@
                     mPreferredActivityHelper, mResolveIntentHelper, mDomainVerificationManager,
                     mDomainVerificationConnection, mInstallerService, mPackageProperty,
                     mResolveComponentName, mInstantAppResolverSettingsComponent,
-                    mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
-                    mSharedSystemSharedLibraryPackageName);
+                    mServicesExtensionPackageName, mSharedSystemSharedLibraryPackageName);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index 98533725..524252c 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -57,11 +57,8 @@
     @GuardedBy("this")
     private final SparseArray<Set<String>> mOwnerProtectedPackages = new SparseArray<>();
 
-    private final Context mContext;
-
     public ProtectedPackages(Context context) {
-        mContext = context;
-        mDeviceProvisioningPackage = mContext.getResources().getString(
+        mDeviceProvisioningPackage = context.getResources().getString(
                 R.string.config_deviceProvisioningPackage);
     }
 
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7bd6a43..3e3b72c 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -67,7 +67,6 @@
     private final PackageManagerService mPm;
     private final IncrementalManager mIncrementalManager;
     private final Installer mInstaller;
-    private final UserManagerInternal mUserManagerInternal;
     private final PermissionManagerServiceInternal mPermissionManager;
     private final SharedLibrariesImpl mSharedLibraries;
     private final AppDataHelper mAppDataHelper;
@@ -79,7 +78,6 @@
         mPm = pm;
         mIncrementalManager = mPm.mInjector.getIncrementalManager();
         mInstaller = mPm.mInjector.getInstaller();
-        mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
         mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
         mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl();
         mAppDataHelper = appDataHelper;
diff --git a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
index 7f6f684..aa52522 100644
--- a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
@@ -31,7 +31,6 @@
  * The access to it must be guarded with the shortcut manager lock.
  */
 public class ShortcutNonPersistentUser {
-    private final ShortcutService mService;
 
     private final int mUserId;
 
@@ -49,8 +48,7 @@
      */
     private final ArraySet<String> mHostPackageSet = new ArraySet<>();
 
-    public ShortcutNonPersistentUser(ShortcutService service, int userId) {
-        mService = service;
+    public ShortcutNonPersistentUser(int userId) {
         mUserId = userId;
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 446c629..96c205c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1378,7 +1378,7 @@
     ShortcutNonPersistentUser getNonPersistentUserLocked(@UserIdInt int userId) {
         ShortcutNonPersistentUser ret = mShortcutNonPersistentUsers.get(userId);
         if (ret == null) {
-            ret = new ShortcutNonPersistentUser(this, userId);
+            ret = new ShortcutNonPersistentUser(userId);
             mShortcutNonPersistentUsers.put(userId, ret);
         }
         return ret;
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 4c42c2d..1d41401 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -141,7 +141,7 @@
                 // If internal storage of the system user fails to prepare on first boot, then
                 // things are *really* broken, so we might as well reboot to recovery right away.
                 try {
-                    Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
+                    Log.e(TAG, "prepareUserData failed for user " + userId, e);
                     if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
                         RecoverySystem.rebootPromptAndWipeUserData(mContext,
                                 "failed to prepare internal storage for system user");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index ee40e18..40f2264 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -77,7 +77,6 @@
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -114,9 +113,6 @@
     /** Internal connection to the package manager */
     private final PackageManagerInternal mPackageManagerInt;
 
-    /** Internal connection to the user manager */
-    private final UserManagerInternal mUserManagerInt;
-
     /** Map of OneTimePermissionUserManagers keyed by userId */
     @GuardedBy("mLock")
     @NonNull
@@ -148,7 +144,6 @@
 
         mContext = context;
         mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
-        mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
 
         mAttributionSourceRegistry = new AttributionSourceRegistry(context);
@@ -1098,12 +1093,10 @@
         private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
 
         private final @NonNull Context mContext;
-        private final @NonNull AppOpsManager mAppOpsManager;
         private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal;
 
         PermissionCheckerService(@NonNull Context context) {
             mContext = context;
-            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
             mPermissionManagerServiceInternal =
                     LocalServices.getService(PermissionManagerServiceInternal.class);
         }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3afba39..6a57362 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -279,7 +279,6 @@
     @NonNull
     private final int[] mGlobalGids;
 
-    private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Context mContext;
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -432,10 +431,10 @@
             }
         }
 
-        mHandlerThread = new ServiceThread(TAG,
+        HandlerThread handlerThread = new ServiceThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
         Watchdog.getInstance().addThread(mHandler);
 
         SystemConfig systemConfig = SystemConfig.getInstance();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e226953..a172de0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -4413,8 +4413,8 @@
 
     private boolean setPowerModeInternal(int mode, boolean enabled) {
         // Maybe filter the event.
-        if (mBatterySaverStateMachine == null || (mode == Mode.LAUNCH && enabled
-                && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled())) {
+        if (mode == Mode.LAUNCH && enabled && mBatterySaverStateMachine != null
+                && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled()) {
             return false;
         }
         return mNativeWrapper.nativeSetPowerMode(mode, enabled);
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 0467d0c..7ab075e 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -38,7 +38,16 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.ad.ITvAdClient;
 import android.media.tv.ad.ITvAdManager;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.ITvAdService;
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSession;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.media.tv.ad.TvAdService;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.AppLinkInfo;
 import android.media.tv.interactive.ITvInteractiveAppClient;
 import android.media.tv.interactive.ITvInteractiveAppManager;
@@ -110,6 +119,8 @@
     @GuardedBy("mLock")
     private boolean mGetServiceListCalled = false;
     @GuardedBy("mLock")
+    private boolean mGetAdServiceListCalled = false;
+    @GuardedBy("mLock")
     private boolean mGetAppLinkInfoListCalled = false;
 
     private final UserManager mUserManager;
@@ -256,6 +267,141 @@
     }
 
     @GuardedBy("mLock")
+    private void buildTvAdServiceListLocked(int userId, String[] updatedPackages) {
+        if (!Flags.enableAdServiceFw()) {
+            return;
+        }
+        UserState userState = getOrCreateUserStateLocked(userId);
+        userState.mPackageSet.clear();
+
+        if (DEBUG) {
+            Slogf.d(TAG, "buildTvAdServiceListLocked");
+        }
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                new Intent(TvAdService.SERVICE_INTERFACE),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                userId);
+        List<TvAdServiceInfo> serviceList = new ArrayList<>();
+
+        for (ResolveInfo ri : services) {
+            ServiceInfo si = ri.serviceInfo;
+            if (!android.Manifest.permission.BIND_TV_AD_SERVICE.equals(si.permission)) {
+                Slog.w(TAG, "Skipping TV AD service " + si.name
+                        + ": it does not require the permission "
+                        + android.Manifest.permission.BIND_TV_AD_SERVICE);
+                continue;
+            }
+
+            ComponentName component = new ComponentName(si.packageName, si.name);
+            try {
+                TvAdServiceInfo info = new TvAdServiceInfo(mContext, component);
+                serviceList.add(info);
+            } catch (Exception e) {
+                Slogf.e(TAG, "failed to load TV AD service " + si.name, e);
+                continue;
+            }
+            userState.mPackageSet.add(si.packageName);
+        }
+
+        // sort the service list by service id
+        Collections.sort(serviceList, Comparator.comparing(TvAdServiceInfo::getId));
+        Map<String, TvAdServiceState> adServiceMap = new HashMap<>();
+        for (TvAdServiceInfo info : serviceList) {
+            String serviceId = info.getId();
+            if (DEBUG) {
+                Slogf.d(TAG, "add " + serviceId);
+            }
+            TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+            if (adServiceState == null) {
+                adServiceState = new TvAdServiceState();
+            }
+            adServiceState.mInfo = info;
+            adServiceState.mUid = getAdServiceUid(info);
+            adServiceState.mComponentName = info.getComponent();
+            adServiceMap.put(serviceId, adServiceState);
+        }
+
+        for (String serviceId : adServiceMap.keySet()) {
+            if (!userState.mAdServiceMap.containsKey(serviceId)) {
+                notifyAdServiceAddedLocked(userState, serviceId);
+            } else if (updatedPackages != null) {
+                // Notify the package updates
+                ComponentName component = adServiceMap.get(serviceId).mInfo.getComponent();
+                for (String updatedPackage : updatedPackages) {
+                    if (component.getPackageName().equals(updatedPackage)) {
+                        updateAdServiceConnectionLocked(component, userId);
+                        notifyAdServiceUpdatedLocked(userState, serviceId);
+                        break;
+                    }
+                }
+            }
+        }
+
+        for (String serviceId : userState.mAdServiceMap.keySet()) {
+            if (!adServiceMap.containsKey(serviceId)) {
+                TvAdServiceInfo info = userState.mAdServiceMap.get(serviceId).mInfo;
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(info.getComponent());
+                if (serviceState != null) {
+                    abortPendingCreateAdSessionRequestsLocked(serviceState, serviceId, userId);
+                }
+                notifyAdServiceRemovedLocked(userState, serviceId);
+            }
+        }
+
+        userState.mIAppMap.clear();
+        userState.mAdServiceMap = adServiceMap;
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceAddedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceAddedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceAdded(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report added AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceRemovedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceRemovedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceRemoved(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report removed AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceUpdatedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceUpdatedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceUpdated(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report updated AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
     private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) {
         if (DEBUG) {
             Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId="
@@ -340,6 +486,16 @@
         }
     }
 
+    private int getAdServiceUid(TvAdServiceInfo info) {
+        try {
+            return getContext().getPackageManager().getApplicationInfo(
+                    info.getServiceInfo().packageName, 0).uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            Slogf.w(TAG, "Unable to get UID for  " + info, e);
+            return Process.INVALID_UID;
+        }
+    }
+
     @Override
     public void onStart() {
         if (DEBUG) {
@@ -357,6 +513,7 @@
             synchronized (mLock) {
                 buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
                 buildAppLinkInfoLocked(mCurrentUserId);
+                buildTvAdServiceListLocked(mCurrentUserId, null);
             }
         }
     }
@@ -372,6 +529,14 @@
                     }
                 }
             }
+            private void buildTvAdServiceList(String[] packages) {
+                int userId = getChangingUserId();
+                synchronized (mLock) {
+                    if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+                        buildTvAdServiceListLocked(userId, packages);
+                    }
+                }
+            }
 
             @Override
             public void onPackageUpdateFinished(String packageName, int uid) {
@@ -379,6 +544,7 @@
                 // This callback is invoked when the TV interactive App service is reinstalled.
                 // In this case, isReplacing() always returns true.
                 buildTvInteractiveAppServiceList(new String[] { packageName });
+                buildTvAdServiceList(new String[] { packageName });
             }
 
             @Override
@@ -390,6 +556,7 @@
                 // available.
                 if (isReplacing()) {
                     buildTvInteractiveAppServiceList(packages);
+                    buildTvAdServiceList(packages);
                 }
             }
 
@@ -403,6 +570,7 @@
                 }
                 if (isReplacing()) {
                     buildTvInteractiveAppServiceList(packages);
+                    buildTvAdServiceList(packages);
                 }
             }
 
@@ -418,6 +586,7 @@
                     return;
                 }
                 buildTvInteractiveAppServiceList(null);
+                buildTvAdServiceList(null);
             }
 
             @Override
@@ -476,6 +645,7 @@
             mCurrentUserId = userId;
             buildTvInteractiveAppServiceListLocked(userId, null);
             buildAppLinkInfoLocked(userId);
+            buildTvAdServiceListLocked(userId, null);
         }
     }
 
@@ -562,6 +732,7 @@
         mRunningProfiles.add(userId);
         buildTvInteractiveAppServiceListLocked(userId, null);
         buildAppLinkInfoLocked(userId);
+        buildTvAdServiceListLocked(userId, null);
     }
 
     @GuardedBy("mLock")
@@ -619,7 +790,19 @@
                 Slog.e(TAG, "error in onSessionReleased", e);
             }
         }
-        removeSessionStateLocked(state.mSessionToken, state.mUserId);
+        removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
+    }
+
+    @GuardedBy("mLock")
+    private void clearAdSessionAndNotifyClientLocked(AdSessionState state) {
+        if (state.mClient != null) {
+            try {
+                state.mClient.onSessionReleased(state.mSeq);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "error in onSessionReleased", e);
+            }
+        }
+        removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
     }
 
     private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
@@ -655,6 +838,44 @@
     }
 
     @GuardedBy("mLock")
+    private AdSessionState getAdSessionStateLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        return getAdSessionStateLocked(sessionToken, callingUid, userState);
+    }
+
+    @GuardedBy("mLock")
+    private AdSessionState getAdSessionStateLocked(IBinder sessionToken, int callingUid,
+            UserState userState) {
+        AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+        if (sessionState == null) {
+            throw new SessionNotFoundException("Session state not found for token " + sessionToken);
+        }
+        // Only the application that requested this session or the system can access it.
+        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+            throw new SecurityException("Illegal access to the session with token " + sessionToken
+                    + " from uid " + callingUid);
+        }
+        return sessionState;
+    }
+
+    @GuardedBy("mLock")
+    private ITvAdSession getAdSessionLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        return getAdSessionLocked(getAdSessionStateLocked(sessionToken, callingUid, userId));
+    }
+
+    @GuardedBy("mLock")
+    private ITvAdSession getAdSessionLocked(AdSessionState sessionState) {
+        ITvAdSession session = sessionState.mSession;
+        if (session == null) {
+            throw new IllegalStateException("Session not yet created for token "
+                    + sessionState.mSessionToken);
+        }
+        return session;
+    }
+
+    @GuardedBy("mLock")
     private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         return getSessionStateLocked(sessionToken, callingUid, userState);
@@ -691,10 +912,200 @@
         return session;
     }
     private final class TvAdBinderService extends ITvAdManager.Stub {
+
+        @Override
+        public List<TvAdServiceInfo> getTvAdServiceList(int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "getTvAdServiceList");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    if (!mGetAdServiceListCalled) {
+                        buildTvAdServiceListLocked(userId, null);
+                        mGetAdServiceListCalled = true;
+                    }
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    List<TvAdServiceInfo> adServiceList = new ArrayList<>();
+                    for (TvAdServiceState state : userState.mAdServiceMap.values()) {
+                        adServiceList.add(state.mInfo);
+                    }
+                    return adServiceList;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void createSession(final ITvAdClient client, final String serviceId, String type,
+                int seq, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+                    userId, "createSession");
+            final long identity = Binder.clearCallingIdentity();
+
+            try {
+                synchronized (mLock) {
+                    if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) {
+                        // Only current user and its running profiles can create sessions.
+                        // Let the client get onConnectionFailed callback for this case.
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvAdServiceState adState = userState.mAdMap.get(serviceId);
+                    if (adState == null) {
+                        Slogf.w(TAG, "Failed to find state for serviceId=" + serviceId);
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+                    AdServiceState serviceState =
+                            userState.mAdServiceStateMap.get(adState.mComponentName);
+                    if (serviceState == null) {
+                        int tasUid = PackageManager.getApplicationInfoAsUserCached(
+                                adState.mComponentName.getPackageName(), 0, resolvedUserId).uid;
+                        serviceState = new AdServiceState(
+                                adState.mComponentName, serviceId, resolvedUserId);
+                        userState.mAdServiceStateMap.put(adState.mComponentName, serviceState);
+                    }
+                    // Send a null token immediately while reconnecting.
+                    if (serviceState.mReconnecting) {
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+
+                    // Create a new session token and a session state.
+                    IBinder sessionToken = new Binder();
+                    AdSessionState sessionState = new AdSessionState(sessionToken, serviceId, type,
+                            adState.mComponentName, client, seq, callingUid,
+                            callingPid, resolvedUserId);
+
+                    // Add them to the global session state map of the current user.
+                    userState.mAdSessionStateMap.put(sessionToken, sessionState);
+
+                    // Also, add them to the session state map of the current service.
+                    serviceState.mSessionTokens.add(sessionToken);
+
+                    if (serviceState.mService != null) {
+                        if (!createAdSessionInternalLocked(serviceState.mService, sessionToken,
+                                resolvedUserId)) {
+                            removeAdSessionStateLocked(sessionToken, resolvedUserId);
+                        }
+                    } else {
+                        updateAdServiceConnectionLocked(adState.mComponentName, resolvedUserId);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void releaseSession(IBinder sessionToken, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "releaseSession");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setSurface");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).setSurface(surface);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in setSurface", e);
+                    }
+                }
+            } finally {
+                if (surface != null) {
+                    // surface is not used in TvInteractiveAppManagerService.
+                    surface.release();
+                }
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
+                int height, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "dispatchSurfaceChanged");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        AdSessionState sessionState = getAdSessionStateLocked(
+                                sessionToken, callingUid, resolvedUserId);
+                        getAdSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
+                                height);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in dispatchSurfaceChanged", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
         @Override
         public void startAdService(IBinder sessionToken, int userId) {
         }
 
+        @Override
+        public void registerCallback(final ITvAdManagerCallback callback, int userId) {
+            int callingPid = Binder.getCallingPid();
+            int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "registerCallback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    if (!userState.mAdCallbacks.register(callback)) {
+                        Slog.e(TAG, "client process has already died");
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void unregisterCallback(ITvAdManagerCallback callback, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "unregisterCallback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    userState.mAdCallbacks.unregister(callback);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
     }
 
     private final class BinderService extends ITvInteractiveAppManager.Stub {
@@ -927,7 +1338,7 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                    releaseAdSessionLocked(sessionToken, callingUid, resolvedUserId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1471,6 +1882,32 @@
         }
 
         @Override
+        public void sendSelectedTrackInfo(IBinder sessionToken, List<TvTrackInfo> tracks,
+                int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendSelectedTrackInfo(tracks=%s)", tracks.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendSelectedTrackInfo");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendSelectedTrackInfo(tracks);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendSelectedTrackInfo", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
@@ -2134,6 +2571,17 @@
     }
 
     @GuardedBy("mLock")
+    private void sendAdSessionTokenToClientLocked(
+            ITvAdClient client, String serviceId, IBinder sessionToken,
+            InputChannel channel, int seq) {
+        try {
+            client.onSessionCreated(serviceId, sessionToken, channel, seq);
+        } catch (RemoteException e) {
+            Slogf.e(TAG, "error in onSessionCreated", e);
+        }
+    }
+
+    @GuardedBy("mLock")
     private boolean createSessionInternalLocked(
             ITvInteractiveAppService service, IBinder sessionToken, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
@@ -2163,6 +2611,58 @@
     }
 
     @GuardedBy("mLock")
+    private boolean createAdSessionInternalLocked(
+            ITvAdService service, IBinder sessionToken, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+        if (DEBUG) {
+            Slogf.d(TAG, "createAdSessionInternalLocked(iAppServiceId="
+                    + sessionState.mAdServiceId + ")");
+        }
+        InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+
+        // Set up a callback to send the session token.
+        ITvAdSessionCallback callback = new AdSessionCallback(sessionState, channels);
+
+        boolean created = true;
+        // Create a session. When failed, send a null token immediately.
+        try {
+            service.createSession(
+                    channels[1], callback, sessionState.mAdServiceId, sessionState.mType);
+        } catch (RemoteException e) {
+            Slogf.e(TAG, "error in createSession", e);
+            sendAdSessionTokenToClientLocked(sessionState.mClient, sessionState.mAdServiceId, null,
+                    null, sessionState.mSeq);
+            created = false;
+        }
+        channels[1].dispose();
+        return created;
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private AdSessionState releaseAdSessionLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        AdSessionState sessionState = null;
+        try {
+            sessionState = getAdSessionStateLocked(sessionToken, callingUid, userId);
+            UserState userState = getOrCreateUserStateLocked(userId);
+            if (sessionState.mSession != null) {
+                sessionState.mSession.asBinder().unlinkToDeath(sessionState, 0);
+                sessionState.mSession.release();
+            }
+        } catch (RemoteException | SessionNotFoundException e) {
+            Slogf.e(TAG, "error in releaseSession", e);
+        } finally {
+            if (sessionState != null) {
+                sessionState.mSession = null;
+            }
+        }
+        removeAdSessionStateLocked(sessionToken, userId);
+        return sessionState;
+    }
+
+    @GuardedBy("mLock")
     @Nullable
     private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
         SessionState sessionState = null;
@@ -2215,6 +2715,36 @@
     }
 
     @GuardedBy("mLock")
+    private void removeAdSessionStateLocked(IBinder sessionToken, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+
+        // Remove the session state from the global session state map of the current user.
+        AdSessionState sessionState = userState.mAdSessionStateMap.remove(sessionToken);
+
+        if (sessionState == null) {
+            Slogf.e(TAG, "sessionState null, no more remove session action!");
+            return;
+        }
+
+        // Also remove the session token from the session token list of the current client and
+        // service.
+        ClientState clientState = userState.mClientStateMap.get(sessionState.mClient.asBinder());
+        if (clientState != null) {
+            clientState.mSessionTokens.remove(sessionToken);
+            if (clientState.isEmpty()) {
+                userState.mClientStateMap.remove(sessionState.mClient.asBinder());
+                sessionState.mClient.asBinder().unlinkToDeath(clientState, 0);
+            }
+        }
+
+        AdServiceState serviceState = userState.mAdServiceStateMap.get(sessionState.mComponent);
+        if (serviceState != null) {
+            serviceState.mSessionTokens.remove(sessionToken);
+        }
+        updateAdServiceConnectionLocked(sessionState.mComponent, userId);
+    }
+
+    @GuardedBy("mLock")
     private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
             String iAppServiceId, int userId) {
         // Let clients know the create session requests are failed.
@@ -2237,6 +2767,28 @@
     }
 
     @GuardedBy("mLock")
+    private void abortPendingCreateAdSessionRequestsLocked(AdServiceState serviceState,
+            String serviceId, int userId) {
+        // Let clients know the create session requests are failed.
+        UserState userState = getOrCreateUserStateLocked(userId);
+        List<AdSessionState> sessionsToAbort = new ArrayList<>();
+        for (IBinder sessionToken : serviceState.mSessionTokens) {
+            AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+            if (sessionState.mSession == null
+                    && (serviceState == null
+                    || sessionState.mAdServiceId.equals(serviceId))) {
+                sessionsToAbort.add(sessionState);
+            }
+        }
+        for (AdSessionState sessionState : sessionsToAbort) {
+            removeAdSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
+            sendAdSessionTokenToClientLocked(sessionState.mClient,
+                    sessionState.mAdServiceId, null, null, sessionState.mSeq);
+        }
+        updateAdServiceConnectionLocked(serviceState.mComponent, userId);
+    }
+
+    @GuardedBy("mLock")
     private void updateServiceConnectionLocked(ComponentName component, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         ServiceState serviceState = userState.mServiceStateMap.get(component);
@@ -2284,10 +2836,64 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void updateAdServiceConnectionLocked(ComponentName component, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        AdServiceState serviceState = userState.mAdServiceStateMap.get(component);
+        if (serviceState == null) {
+            return;
+        }
+        if (serviceState.mReconnecting) {
+            if (!serviceState.mSessionTokens.isEmpty()) {
+                // wait until all the sessions are removed.
+                return;
+            }
+            serviceState.mReconnecting = false;
+        }
+
+        boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
+                || (!serviceState.mPendingAppLinkCommand.isEmpty());
+
+        if (serviceState.mService == null && shouldBind) {
+            // This means that the service is not yet connected but its state indicates that we
+            // have pending requests. Then, connect the service.
+            if (serviceState.mBound) {
+                // We have already bound to the service so we don't try to bind again until after we
+                // unbind later on.
+                return;
+            }
+            if (DEBUG) {
+                Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
+            }
+
+            Intent i = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
+            serviceState.mBound = mContext.bindServiceAsUser(
+                    i, serviceState.mConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+                    new UserHandle(userId));
+        } else if (serviceState.mService != null && !shouldBind) {
+            // This means that the service is already connected but its state indicates that we have
+            // nothing to do with it. Then, disconnect the service.
+            if (DEBUG) {
+                Slogf.d(TAG, "unbindService(service=" + component + ")");
+            }
+            mContext.unbindService(serviceState.mConnection);
+            userState.mAdServiceStateMap.remove(component);
+        }
+    }
+
     private static final class UserState {
         private final int mUserId;
+        // A mapping from the TV AD service ID to its TvAdServiceState.
+        private Map<String, TvAdServiceState> mAdMap = new HashMap<>();
+        // A mapping from the name of a TV Interactive App service to its state.
+        private final Map<ComponentName, AdServiceState> mAdServiceStateMap = new HashMap<>();
+        // A mapping from the token of a TV Interactive App session to its state.
+        private final Map<IBinder, AdSessionState> mAdSessionStateMap = new HashMap<>();
         // A mapping from the TV Interactive App ID to its TvInteractiveAppState.
         private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>();
+        // A mapping from the TV AD service ID to its TvAdServiceState.
+        private Map<String, TvAdServiceState> mAdServiceMap = new HashMap<>();
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
         // A mapping from the name of a TV Interactive App service to its state.
@@ -2299,6 +2905,8 @@
         private final Set<String> mPackageSet = new HashSet<>();
         // A list of all app link infos.
         private final List<AppLinkInfo> mAppLinkInfoList = new ArrayList<>();
+        private final RemoteCallbackList<ITvAdManagerCallback> mAdCallbacks =
+                new RemoteCallbackList<>();
 
         // A list of callbacks.
         private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
@@ -2317,7 +2925,16 @@
         private int mIAppNumber;
     }
 
+    private static final class TvAdServiceState {
+        private String mAdServiceId;
+        private ComponentName mComponentName;
+        private TvAdServiceInfo mInfo;
+        private int mUid;
+        private int mAdNumber;
+    }
+
     private final class SessionState implements IBinder.DeathRecipient {
+        // TODO: rename SessionState and reorganize classes / methods of this file
         private final IBinder mSessionToken;
         private ITvInteractiveAppSession mSession;
         private final String mIAppServiceId;
@@ -2359,6 +2976,49 @@
         }
     }
 
+    private final class AdSessionState implements IBinder.DeathRecipient {
+        private final IBinder mSessionToken;
+        private ITvAdSession mSession;
+        private final String mAdServiceId;
+
+        private final String mType;
+        private final ITvAdClient mClient;
+        private final int mSeq;
+        private final ComponentName mComponent;
+
+        // The UID of the application that created the session.
+        // The application is usually the TV app.
+        private final int mCallingUid;
+
+        // The PID of the application that created the session.
+        // The application is usually the TV app.
+        private final int mCallingPid;
+
+        private final int mUserId;
+
+        private AdSessionState(IBinder sessionToken, String serviceId, String type,
+                ComponentName componentName, ITvAdClient client, int seq,
+                int callingUid, int callingPid, int userId) {
+            mSessionToken = sessionToken;
+            mAdServiceId = serviceId;
+            mType = type;
+            mComponent = componentName;
+            mClient = client;
+            mSeq = seq;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            mUserId = userId;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mSession = null;
+                clearAdSessionAndNotifyClientLocked(this);
+            }
+        }
+    }
+
     private final class ClientState implements IBinder.DeathRecipient {
         private final List<IBinder> mSessionTokens = new ArrayList<>();
 
@@ -2429,6 +3089,29 @@
         }
     }
 
+    private final class AdServiceState {
+        private final List<IBinder> mSessionTokens = new ArrayList<>();
+        private final ServiceConnection mConnection;
+        private final ComponentName mComponent;
+        private final String mAdServiceId;
+        private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
+
+        private ITvAdService mService;
+        private AdServiceCallback mCallback;
+        private boolean mBound;
+        private boolean mReconnecting;
+
+        private AdServiceState(ComponentName component, String tasId, int userId) {
+            mComponent = component;
+            mConnection = new AdServiceConnection(component, userId);
+            mAdServiceId = tasId;
+        }
+
+        private void addPendingAppLinkCommand(Bundle command) {
+            mPendingAppLinkCommand.add(command);
+        }
+    }
+
     private final class InteractiveAppServiceConnection implements ServiceConnection {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -2542,6 +3225,98 @@
         }
     }
 
+    private final class AdServiceConnection implements ServiceConnection {
+        private final ComponentName mComponent;
+        private final int mUserId;
+
+        private AdServiceConnection(ComponentName component, int userId) {
+            mComponent = component;
+            mUserId = userId;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder service) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onServiceConnected(component=" + component + ")");
+            }
+            synchronized (mLock) {
+                UserState userState = getUserStateLocked(mUserId);
+                if (userState == null) {
+                    // The user was removed while connecting.
+                    mContext.unbindService(this);
+                    return;
+                }
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+                serviceState.mService = ITvAdService.Stub.asInterface(service);
+
+                // Register a callback, if we need to.
+                if (serviceState.mCallback == null) {
+                    serviceState.mCallback = new AdServiceCallback(mComponent, mUserId);
+                    try {
+                        serviceState.mService.registerCallback(serviceState.mCallback);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in registerCallback", e);
+                    }
+                }
+
+                if (!serviceState.mPendingAppLinkCommand.isEmpty()) {
+                    for (Iterator<Bundle> it = serviceState.mPendingAppLinkCommand.iterator();
+                            it.hasNext(); ) {
+                        Bundle command = it.next();
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            serviceState.mService.sendAppLinkCommand(command);
+                            it.remove();
+                        } catch (RemoteException e) {
+                            Slogf.e(TAG, "error in sendAppLinkCommand(" + command
+                                    + ") when onServiceConnected", e);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }
+
+                List<IBinder> tokensToBeRemoved = new ArrayList<>();
+
+                // And create sessions, if any.
+                for (IBinder sessionToken : serviceState.mSessionTokens) {
+                    if (!createAdSessionInternalLocked(
+                            serviceState.mService, sessionToken, mUserId)) {
+                        tokensToBeRemoved.add(sessionToken);
+                    }
+                }
+
+                for (IBinder sessionToken : tokensToBeRemoved) {
+                    removeAdSessionStateLocked(sessionToken, mUserId);
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onServiceDisconnected(component=" + component + ")");
+            }
+            if (!mComponent.equals(component)) {
+                throw new IllegalArgumentException("Mismatched ComponentName: "
+                        + mComponent + " (expected), " + component + " (actual).");
+            }
+            synchronized (mLock) {
+                UserState userState = getOrCreateUserStateLocked(mUserId);
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+                if (serviceState != null) {
+                    serviceState.mReconnecting = true;
+                    serviceState.mBound = false;
+                    serviceState.mService = null;
+                    serviceState.mCallback = null;
+
+                    abortPendingCreateAdSessionRequestsLocked(serviceState, null, mUserId);
+                }
+            }
+        }
+    }
+
+
     private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -2567,6 +3342,17 @@
         }
     }
 
+
+    private final class AdServiceCallback extends ITvAdServiceCallback.Stub {
+        private final ComponentName mComponent;
+        private final int mUserId;
+
+        AdServiceCallback(ComponentName component, int userId) {
+            mComponent = component;
+            mUserId = userId;
+        }
+    }
+
     private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub {
         private final SessionState mSessionState;
         private final InputChannel[] mInputChannels;
@@ -2798,6 +3584,23 @@
         }
 
         @Override
+        public void onRequestSelectedTrackInfo() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestSelectedTrackInfo");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestSelectedTrackInfo(mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestSelectedTrackInfo", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentTvInputId() {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -3110,6 +3913,85 @@
         }
     }
 
+    private final class AdSessionCallback extends ITvAdSessionCallback.Stub {
+        private final AdSessionState mSessionState;
+        private final InputChannel[] mInputChannels;
+
+        AdSessionCallback(AdSessionState sessionState, InputChannel[] channels) {
+            mSessionState = sessionState;
+            mInputChannels = channels;
+        }
+
+        @Override
+        public void onSessionCreated(ITvAdSession session) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onSessionCreated(adServiceId="
+                        + mSessionState.mAdServiceId + ")");
+            }
+            synchronized (mLock) {
+                mSessionState.mSession = session;
+                if (session != null && addAdSessionTokenToClientStateLocked(session)) {
+                    sendAdSessionTokenToClientLocked(
+                            mSessionState.mClient,
+                            mSessionState.mAdServiceId,
+                            mSessionState.mSessionToken,
+                            mInputChannels[0],
+                            mSessionState.mSeq);
+                } else {
+                    removeAdSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId);
+                    sendAdSessionTokenToClientLocked(mSessionState.mClient,
+                            mSessionState.mAdServiceId, null, null, mSessionState.mSeq);
+                }
+                mInputChannels[0].dispose();
+            }
+        }
+
+        @Override
+        public void onLayoutSurface(int left, int top, int right, int bottom) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
+                            + ", right=" + right + ", bottom=" + bottom + ",)");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onLayoutSurface(left, top, right, bottom,
+                            mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onLayoutSurface", e);
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
+            try {
+                session.asBinder().linkToDeath(mSessionState, 0);
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "session process has already died", e);
+                return false;
+            }
+
+            IBinder clientToken = mSessionState.mClient.asBinder();
+            UserState userState = getOrCreateUserStateLocked(mSessionState.mUserId);
+            ClientState clientState = userState.mClientStateMap.get(clientToken);
+            if (clientState == null) {
+                clientState = new ClientState(clientToken, mSessionState.mUserId);
+                try {
+                    clientToken.linkToDeath(clientState, 0);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "client process has already died", e);
+                    return false;
+                }
+                userState.mClientStateMap.put(clientToken, clientState);
+            }
+            clientState.mSessionTokens.add(mSessionState.mSessionToken);
+            return true;
+        }
+    }
+
     private static class SessionNotFoundException extends IllegalArgumentException {
         SessionNotFoundException(String name) {
             super(name);
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
new file mode 100644
index 0000000..5f488b7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.ContentRecordingSession;
+import android.window.IScreenRecordingCallback;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Set;
+
+public class ScreenRecordingCallbackController {
+
+    private final class Callback implements IBinder.DeathRecipient {
+
+        IScreenRecordingCallback mCallback;
+        int mUid;
+
+        Callback(IScreenRecordingCallback callback, int uid) {
+            this.mCallback = callback;
+            this.mUid = uid;
+        }
+
+        public void binderDied() {
+            unregister(mCallback);
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+
+    private final WindowManagerService mWms;
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private WindowContainer<WindowContainer> mRecordedWC;
+
+    private boolean mWatcherCallbackRegistered = false;
+
+    private final class MediaProjectionWatcherCallback extends
+            IMediaProjectionWatcherCallback.Stub {
+        @Override
+        public void onStart(MediaProjectionInfo mediaProjectionInfo) {
+            onScreenRecordingStart(mediaProjectionInfo);
+        }
+
+        @Override
+        public void onStop(MediaProjectionInfo mediaProjectionInfo) {
+            onScreenRecordingStop();
+        }
+
+        @Override
+        public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo,
+                ContentRecordingSession contentRecordingSession) {
+        }
+    }
+
+    ScreenRecordingCallbackController(WindowManagerService wms) {
+        mWms = wms;
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void setRecordedWindowContainer(MediaProjectionInfo mediaProjectionInfo) {
+        if (mediaProjectionInfo.getLaunchCookie() == null) {
+            mRecordedWC = (WindowContainer) mWms.mRoot.getDefaultDisplay();
+        } else {
+            mRecordedWC = mWms.mRoot.getActivity(activity -> activity.mLaunchCookie
+                    == mediaProjectionInfo.getLaunchCookie()).getTask();
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void ensureMediaProjectionWatcherCallbackRegistered() {
+        if (mWatcherCallbackRegistered) {
+            return;
+        }
+
+        IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+        IMediaProjectionManager mediaProjectionManager =
+                IMediaProjectionManager.Stub.asInterface(binder);
+
+        long identityToken = Binder.clearCallingIdentity();
+        MediaProjectionInfo mediaProjectionInfo = null;
+        try {
+            mediaProjectionInfo = mediaProjectionManager.addCallback(
+                    new MediaProjectionWatcherCallback());
+            mWatcherCallbackRegistered = true;
+        } catch (RemoteException e) {
+            ProtoLog.e(WM_ERROR, "Failed to register MediaProjectionWatcherCallback");
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+        }
+
+        if (mediaProjectionInfo != null) {
+            setRecordedWindowContainer(mediaProjectionInfo);
+        }
+    }
+
+    boolean register(IScreenRecordingCallback callback) {
+        synchronized (mWms.mGlobalLock) {
+            ensureMediaProjectionWatcherCallbackRegistered();
+
+            IBinder binder = callback.asBinder();
+            int uid = Binder.getCallingUid();
+
+            if (mCallbacks.containsKey(binder)) {
+                return mLastInvokedStateByUid.get(uid);
+            }
+
+            Callback callbackInfo = new Callback(callback, uid);
+            try {
+                binder.linkToDeath(callbackInfo, 0);
+            } catch (RemoteException e) {
+                return false;
+            }
+
+            boolean uidInRecording = uidHasRecordedActivity(callbackInfo.mUid);
+            mLastInvokedStateByUid.put(callbackInfo.mUid, uidInRecording);
+            mCallbacks.put(binder, callbackInfo);
+            return uidInRecording;
+        }
+    }
+
+    void unregister(IScreenRecordingCallback callback) {
+        synchronized (mWms.mGlobalLock) {
+            IBinder binder = callback.asBinder();
+            Callback callbackInfo = mCallbacks.remove(binder);
+            binder.unlinkToDeath(callbackInfo, 0);
+
+            boolean uidHasCallback = false;
+            for (Callback cb : mCallbacks.values()) {
+                if (cb.mUid == callbackInfo.mUid) {
+                    uidHasCallback = true;
+                    break;
+                }
+            }
+            if (!uidHasCallback) {
+                mLastInvokedStateByUid.remove(callbackInfo.mUid);
+            }
+        }
+    }
+
+    private void onScreenRecordingStart(MediaProjectionInfo mediaProjectionInfo) {
+        synchronized (mWms.mGlobalLock) {
+            setRecordedWindowContainer(mediaProjectionInfo);
+            dispatchCallbacks(getRecordedUids(), true /* visibleInScreenRecording*/);
+        }
+    }
+
+    private void onScreenRecordingStop() {
+        synchronized (mWms.mGlobalLock) {
+            dispatchCallbacks(getRecordedUids(), false /*visibleInScreenRecording*/);
+            mRecordedWC = null;
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    void onProcessActivityVisibilityChanged(int uid, boolean processVisible) {
+        // If recording isn't active or there's no registered callback for the uid, there's nothing
+        // to do on this visibility change.
+        if (mRecordedWC == null || !mLastInvokedStateByUid.containsKey(uid)) {
+            return;
+        }
+
+        // If the callbacks are already in the correct state, avoid making duplicate callbacks for
+        // the same state. This can happen when:
+        // * a process becomes visible but its UID already has a recorded activity from another
+        //   process.
+        // * a process becomes invisible but its UID already doesn't have any recorded activities.
+        if (processVisible == mLastInvokedStateByUid.get(uid)) {
+            return;
+        }
+
+        // If the process visibility change doesn't change the visibility of the UID, avoid making
+        // duplicate callbacks for the same state. This can happen when:
+        // * a process becomes visible but the newly visible activity isn't in the recorded window
+        //   container.
+        // * a process becomes invisible but there are still activities being recorded for the UID.
+        boolean uidInRecording = uidHasRecordedActivity(uid);
+        if ((processVisible && !uidInRecording) || (!processVisible && uidInRecording)) {
+            return;
+        }
+
+        dispatchCallbacks(Set.of(uid), processVisible);
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private boolean uidHasRecordedActivity(int uid) {
+        if (mRecordedWC == null) {
+            return false;
+        }
+        boolean[] hasRecordedActivity = {false};
+        mRecordedWC.forAllActivities(activityRecord -> {
+            if (activityRecord.getUid() == uid && activityRecord.isVisibleRequested()) {
+                hasRecordedActivity[0] = true;
+                return true;
+            }
+            return false;
+        }, true /*traverseTopToBottom*/);
+        return hasRecordedActivity[0];
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private Set<Integer> getRecordedUids() {
+        Set<Integer> result = new ArraySet<>();
+        if (mRecordedWC == null) {
+            return result;
+        }
+        mRecordedWC.forAllActivities(activityRecord -> {
+            if (activityRecord.isVisibleRequested() && mLastInvokedStateByUid.containsKey(
+                    activityRecord.getUid())) {
+                result.add(activityRecord.getUid());
+            }
+        }, true /*traverseTopToBottom*/);
+        return result;
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+        if (uids.isEmpty()) {
+            return;
+        }
+
+        for (Integer uid : uids) {
+            mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+        }
+
+        for (Callback callback : mCallbacks.values()) {
+            if (!uids.contains(callback.mUid)) {
+                continue;
+            }
+            try {
+                callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
+            } catch (RemoteException e) {
+                // Client has died. Cleanup is handled via DeathRecipient.
+            }
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        pw.format("ScreenRecordingCallbackController:\n");
+        pw.format("  Registered callbacks:\n");
+        for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
+            pw.format("    callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+        }
+        pw.format("  Last invoked states:\n");
+        for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
+            pw.format("    uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
+                    entry.getValue());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b38dc00..9179acf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,6 +303,7 @@
 import android.view.inputmethod.ImeTracker;
 import android.window.AddToSurfaceSyncGroupResult;
 import android.window.ClientWindowFrames;
+import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
@@ -1104,6 +1105,8 @@
         void onAppFreezeTimeout();
     }
 
+    private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
+
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
@@ -1345,6 +1348,7 @@
         mBlurController = new BlurController(mContext, mPowerManager);
         mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
         mAccessibilityController = new AccessibilityController(this);
+        mScreenRecordingCallbackController = new ScreenRecordingCallbackController(this);
         mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
             synchronized (mGlobalLock) {
                 DisplayContent dc = mRoot.getDisplayContent(displayId);
@@ -7188,6 +7192,7 @@
             mSystemPerformanceHinter.dump(pw, "");
             mTrustedPresentationListenerController.dump(pw);
             mSensitiveContentPackages.dump(pw);
+            mScreenRecordingCallbackController.dump(pw);
         }
     }
 
@@ -9889,4 +9894,18 @@
             int id) {
         mTrustedPresentationListenerController.unregisterListener(listener, id);
     }
+
+    @Override
+    public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+        return mScreenRecordingCallbackController.register(callback);
+    }
+
+    @Override
+    public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+        mScreenRecordingCallbackController.unregister(callback);
+    }
+
+    void onProcessActivityVisibilityChanged(int uid, boolean visible) {
+        mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b8fa5e5..6d2e8cc 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1271,8 +1271,10 @@
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
         if (!wasAnyVisible && anyVisible) {
             mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
+            mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/);
         } else if (wasAnyVisible && !anyVisible) {
             mAtm.mVisibleActivityProcessTracker.onAllActivitiesInvisible(this);
+            mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, false /*visible*/);
         } else if (wasAnyVisible && !wasResumed && hasResumedActivity()) {
             mAtm.mVisibleActivityProcessTracker.onActivityResumedWhileVisible(this);
         }
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 4c37f7d..b84ff29 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.FlaggedApi;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,6 +47,7 @@
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.lang.annotation.Retention;
@@ -152,12 +154,36 @@
     public static final long CAPABILITY_TERMINAL_BASED_CALL_WAITING = 1 << 2;
 
     /**
+     * This ImsService supports the capability to manage calls on multiple subscriptions at the same
+     * time.
+     * <p>
+     * When set, this ImsService supports managing calls on multiple subscriptions at the same time
+     * for all WLAN network configurations. Telephony will allow new outgoing/incoming IMS calls to
+     * be set up on other subscriptions while there is an ongoing call. The ImsService must also
+     * support managing calls on WWAN + WWAN configurations whenever the modem also reports
+     * simultaneous calling availability, which can be listened to using the
+     * {@link android.telephony.TelephonyCallback.SimultaneousCellularCallingSupportListener} API.
+     * Telephony will only allow additional ongoing/incoming IMS calls on another subscription to be
+     * set up on WWAN + WWAN configurations when the modem reports that simultaneous cellular
+     * calling is allowed at the current time on both subscriptions where there are ongoing calls.
+     * <p>
+     * When unset (default), this ImsService can not support calls on multiple subscriptions at the
+     * same time for any WLAN or WWAN configurations, so pending outgoing call placed on another
+     * cellular subscription while there is an ongoing call will be cancelled by Telephony.
+     * Similarly, any incoming call notification on another cellular subscription while there is an
+     * ongoing call will be rejected.
+     * @hide TODO: move this to system API when we have a backing implementation + CTS testing
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    public static final long CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING = 1 << 3;
+
+    /**
      * Used for internal correctness checks of capabilities set by the ImsService implementation and
      * tracks the index of the largest defined flag in the capabilities long.
      * @hide
      */
     public static final long CAPABILITY_MAX_INDEX =
-            Long.numberOfTrailingZeros(CAPABILITY_TERMINAL_BASED_CALL_WAITING);
+            Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
 
     /**
      * @hide