diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 08a09e1b1a73b3af5f3b6ba0fae7f80c4fbf62c6..b1f587e45a6d684c56ff87ac33f8e2bf2bf1e00c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -334,7 +334,7 @@ java_aconfig_library {
 aconfig_declarations {
     name: "android.app.flags-aconfig",
     package: "android.app",
-    srcs: ["core/java/android/app/activity_manager.aconfig"],
+    srcs: ["core/java/android/app/*.aconfig"],
 }
 
 java_aconfig_library {
diff --git a/boot/Android.bp b/boot/Android.bp
index 93d425e439a9353cb57ede40e809a8944239f0d5..b33fab6e1a9fa40f57e4571d7f4eb078f9455972 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -208,6 +208,13 @@ custom_platform_bootclasspath {
     ],
 }
 
+genrule { // This module exists to make the srcjar output available to Make.
+    name: "platform-bootclasspath.srcjar",
+    srcs: [":platform-bootclasspath{.srcjar}"],
+    out: ["platform-bootclasspath.srcjar"],
+    cmd: "cp $(in) $(out)",
+}
+
 platform_systemserverclasspath {
     name: "platform-systemserverclasspath",
 }
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 82df55509a7ad5bbd0d66ab2a5c73ab72512edd1..e7361fe95e8ed52e65556c7b3c743cbeba891bcb 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -48,6 +48,7 @@ Register a new uinput device
 | `vid`            | 16-bit integer | Vendor ID                  |
 | `pid`            | 16-bit integer | Product ID                 |
 | `bus`            | string         | Bus that device should use |
+| `port`           | string         | `phys` value to report     |
 | `configuration`  | object array   | uinput device configuration|
 | `ff_effects_max` | integer        | `ff_effects_max` value     |
 | `abs_info`       | array          | Absolute axes information  |
diff --git a/core/api/current.txt b/core/api/current.txt
index c9f06397abbf2e26013cf68b51204507562ad08e..43c8214e974973adddee0937c630ce5e991a33db 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5952,6 +5952,7 @@ package android.app {
 
   public class GrammaticalInflectionManager {
     method public int getApplicationGrammaticalGender();
+    method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
     method public void setRequestedApplicationGrammaticalGender(int);
   }
 
@@ -42903,7 +42904,7 @@ package android.telephony {
     field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
     field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
-    field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
     field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
     field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
@@ -43071,6 +43072,7 @@ package android.telephony {
     field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool";
     field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
     field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
     field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
     field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
     field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1d88e002b4bddc1125457c4b0e1744121409d4ae..98a454ad3eabd9508484da205bb0485b88670599 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -16731,17 +16731,22 @@ package android.telephony.satellite {
   }
 
   public final class SatelliteManager {
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -16768,6 +16773,7 @@ package android.telephony.satellite {
     field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
     field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
     field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
     field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 25c48e630380cb00dbe58633c04c6439ee7aa77d..183783bdf3096ef358d4452b2bd5da487b83a8da 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -62,7 +62,7 @@ import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ActivityResultItem;
 import android.app.servertransaction.ClientTransaction;
-import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PendingTransactionActions;
 import android.app.servertransaction.PendingTransactionActions.StopInfo;
@@ -375,8 +375,8 @@ public final class ActivityThread extends ClientTransactionHandler
     @GuardedBy("mPendingOverrideConfigs")
     private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>();
     /** The activities to be truly destroyed (not include relaunch). */
-    final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
-            Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
+    final Map<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed =
+            Collections.synchronizedMap(new ArrayMap<>());
     // List of new activities that should be reported when next we idle.
     final ArrayList<ActivityClientRecord> mNewActivities = new ArrayList<>();
     // Number of activities that are currently visible on-screen.
@@ -5799,7 +5799,7 @@ public final class ActivityThread extends ClientTransactionHandler
     }
 
     @Override
-    public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() {
+    public Map<IBinder, DestroyActivityItem> getActivitiesToBeDestroyed() {
         return mActivitiesToBeDestroyed;
     }
 
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 98020ff2d173b6ca8d7764fb362f6b7003001e9b..25075e91088fe1abd8ac28fa74b86ac639b36d41 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -19,7 +19,7 @@ import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.servertransaction.ClientTransaction;
-import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.PendingTransactionActions;
 import android.app.servertransaction.TransactionExecutor;
 import android.content.Context;
@@ -108,7 +108,7 @@ public abstract class ClientTransactionHandler {
     // and deliver callbacks.
 
     /** Get activity and its corresponding transaction item which are going to destroy. */
-    public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
+    public abstract Map<IBinder, DestroyActivityItem> getActivitiesToBeDestroyed();
 
     /** Destroy the activity. */
     public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 1905b6a46d7e85da0fa0b6225ccf961fe7a425e8..bc6fe614676458a3813421d94df03268fb6beb5a 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,12 +16,15 @@
 
 package android.app;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.RemoteException;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -31,11 +34,15 @@ import java.util.Set;
  */
 @SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
 public class GrammaticalInflectionManager {
-    private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+
+    /** @hide */
+    @NonNull
+    public static final Set<Integer> VALID_GRAMMATICAL_GENDER_VALUES =
+        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
             Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
             Configuration.GRAMMATICAL_GENDER_NEUTRAL,
             Configuration.GRAMMATICAL_GENDER_FEMININE,
-            Configuration.GRAMMATICAL_GENDER_MASCULINE));
+            Configuration.GRAMMATICAL_GENDER_MASCULINE)));
 
     private final Context mContext;
     private final IGrammaticalInflectionManager mService;
@@ -79,7 +86,7 @@ public class GrammaticalInflectionManager {
      */
     public void setRequestedApplicationGrammaticalGender(
             @Configuration.GrammaticalGender int grammaticalGender) {
-        if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+        if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
             throw new IllegalArgumentException("Unknown grammatical gender");
         }
 
@@ -90,4 +97,44 @@ public class GrammaticalInflectionManager {
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the current grammatical gender for all privileged applications. The value will be
+     * stored in an encrypted file at {@link android.os.Environment#getDataSystemCeDirectory(int)
+     *
+     * @param grammaticalGender the terms of address the user preferred in system.
+     *
+     * @see Configuration#getGrammaticalGender
+     * @hide
+     */
+    public void setSystemWideGrammaticalGender(
+            @Configuration.GrammaticalGender int grammaticalGender) {
+        if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) {
+            throw new IllegalArgumentException("Unknown grammatical gender");
+        }
+
+        try {
+            mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the current grammatical gender of privileged application from the encrypted file,
+     * which is stored under {@link android.os.Environment#getDataSystemCeDirectory(int)}.
+     *
+     * @return the value of grammatical gender
+     *
+     * @see Configuration#getGrammaticalGender
+     */
+    @FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
+    @Configuration.GrammaticalGender
+    public int getSystemGrammaticalGender() {
+        try {
+            return mService.getSystemGrammaticalGender(mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 9366a45551da1bac94e268fd25ccc4e98b909a02..48a48416d59217c0aa47f427981d0f5af22a23a5 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -16,4 +16,14 @@ package android.app;
       * Sets a specified app’s app-specific grammatical gender.
       */
      void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
- }
\ No newline at end of file
+
+     /**
+      * Sets the grammatical gender to system.
+      */
+     void setSystemWideGrammaticalGender(int userId, int gender);
+
+     /**
+      * Gets the grammatical gender from system.
+      */
+     int getSystemGrammaticalGender(int userId);
+ }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e1c45d98e6787c36f29880db7660bf359f17ab33..9cf54e3b906367d2911fb2fd88b7c1479c24d691 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -54,6 +54,9 @@ per-file IBackupAgent.aidl = file:/services/backup/OWNERS
 per-file Broadcast* = file:/BROADCASTS_OWNERS
 per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
 
+# GrammaticalInflectionManager
+per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
+
 # KeyguardManager
 per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
 
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 315a0556042abf2b3983a81ac5ab58c28cafdd96..a29c196d88de21a0a3f1fd915415b2cdced1d92e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -19,7 +19,7 @@
             "name": "CtsAppOpsTestCases",
             "options": [
                 {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
                 }
             ]
         },
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
new file mode 100644
index 0000000000000000000000000000000000000000..989ce61337a3bfa647510f89081e11b02ac60094
--- /dev/null
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+    name: "system_terms_of_address_enabled"
+    namespace: "grammatical_gender"
+    description: "Feature flag for System Terms of Address"
+    bug: "297798866"
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index a5b0f18dad3b19cb97cf957bbec12218c1000306..8617386516afb7ccc88baccb39104870fdb04dc5 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -23,7 +23,6 @@ import android.annotation.Nullable;
 import android.app.ClientTransactionHandler;
 import android.app.IApplicationThread;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -83,23 +82,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
         return mActivityCallbacks;
     }
 
-    /** Get the target activity. */
-    @Nullable
-    @UnsupportedAppUsage
-    public IBinder getActivityToken() {
-        // TODO(b/260873529): remove after we allow multiple activity items in one transaction.
-        if (mLifecycleStateRequest != null) {
-            return mLifecycleStateRequest.getActivityToken();
-        }
-        for (int i = mActivityCallbacks.size() - 1; i >= 0; i--) {
-            final IBinder token = mActivityCallbacks.get(i).getActivityToken();
-            if (token != null) {
-                return token;
-            }
-        }
-        return null;
-    }
-
     /** Get the target state lifecycle request. */
     @VisibleForTesting(visibility = PACKAGE)
     @UnsupportedAppUsage
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index ddb6df10517cfe6c79d007eacd44a3a244c43c1d..f9cf075d60624c094de6b4947f0459b368232e09 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -49,6 +49,13 @@ public class DestroyActivityItem extends ActivityLifecycleItem {
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
+    @Override
+    public void postExecute(@NonNull ClientTransactionHandler client,
+            @NonNull PendingTransactionActions pendingActions) {
+        // Cleanup after execution.
+        client.getActivitiesToBeDestroyed().remove(getActivityToken());
+    }
+
     @Override
     public int getTargetState() {
         return ON_DESTROY;
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 44336735254df606abee2cf5f5ca9eb1eb5f4774..066f9fe8497042505b74134346e05648d0ab9447 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -47,7 +47,6 @@ import android.util.Slog;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * Class that manages transaction execution in the correct order.
@@ -75,34 +74,14 @@ public class TransactionExecutor {
      * either remain in the initial state, or last state needed by a callback.
      */
     public void execute(@NonNull ClientTransaction transaction) {
-        if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction");
-
-        final IBinder token = transaction.getActivityToken();
-        if (token != null) {
-            final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed =
-                    mTransactionHandler.getActivitiesToBeDestroyed();
-            final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token);
-            if (destroyItem != null) {
-                if (transaction.getLifecycleStateRequest() == destroyItem) {
-                    // It is going to execute the transaction that will destroy activity with the
-                    // token, so the corresponding to-be-destroyed record can be removed.
-                    activitiesToBeDestroyed.remove(token);
-                }
-                if (mTransactionHandler.getActivityClient(token) == null) {
-                    // The activity has not been created but has been requested to destroy, so all
-                    // transactions for the token are just like being cancelled.
-                    Slog.w(TAG, tId(transaction) + "Skip pre-destroyed transaction:\n"
-                            + transactionToString(transaction, mTransactionHandler));
-                    return;
-                }
-            }
+        if (DEBUG_RESOLVER) {
+            Slog.d(TAG, tId(transaction) + "Start resolving transaction");
+            Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
         }
 
-        if (DEBUG_RESOLVER) Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
-
         executeCallbacks(transaction);
-
         executeLifecycleState(transaction);
+
         mPendingActions.clear();
         if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
     }
@@ -135,6 +114,14 @@ public class TransactionExecutor {
             final IBinder token = item.getActivityToken();
             ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
 
+            if (token != null && r == null
+                    && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+                // The activity has not been created but has been requested to destroy, so all
+                // transactions for the token are just like being cancelled.
+                Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+                continue;
+            }
+
             if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
             final int postExecutionState = item.getPostExecutionState();
 
@@ -211,6 +198,10 @@ public class TransactionExecutor {
         }
 
         if (r == null) {
+            if (mTransactionHandler.getActivitiesToBeDestroyed().get(token) == lifecycleItem) {
+                // Always cleanup for destroy item.
+                lifecycleItem.postExecute(mTransactionHandler, mPendingActions);
+            }
             // Ignore requests for non-existent client records for now.
             return;
         }
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index ab669cc141f1abe4660cb90c306b40dfa1d302b5..b0ab11f488580ea7aa7782e9b45b7099839615e1 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -45,7 +45,8 @@
             ]
         },
         {
-            "name":"CarrierAppIntegrationTestCases"
+            "name":"CarrierAppIntegrationTestCases",
+            "keywords": ["internal"]
         },
         {
             "name":"CtsSilentUpdateHostTestCases"
diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java
index af9a73ca31ee50fde5fe00d49f3bedbbfbb61750..53a3ea516530f21ed578fdb3284c9b98d3404486 100644
--- a/core/java/android/net/metrics/WakeupEvent.java
+++ b/core/java/android/net/metrics/WakeupEvent.java
@@ -29,7 +29,7 @@ public class WakeupEvent {
     public String iface;
     public int uid;
     public int ethertype;
-    public MacAddress dstHwAddr;
+    public MacAddress dstHwAddr;  // actually used to store a src mac address
     public String srcIp;
     public String dstIp;
     public int ipNextHeader;
@@ -44,7 +44,7 @@ public class WakeupEvent {
         j.add(iface);
         j.add("uid: " + Integer.toString(uid));
         j.add("eth=0x" + Integer.toHexString(ethertype));
-        j.add("dstHw=" + dstHwAddr);
+        j.add("srcMac=" + dstHwAddr);  // really!! http://b/292404319#comment11
         if (ipNextHeader > 0) {
             j.add("ipNxtHdr=" + ipNextHeader);
             j.add("srcIp=" + srcIp);
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index ad3abd9b531c181d2518db94f55db4c320fb43a0..2d6e09a1b268eadb5e0ebf6f1c9f8643b023c1fe 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -8,7 +8,6 @@
       "name": "FrameworksVibratorCoreTests",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
@@ -21,7 +20,6 @@
       "name": "FrameworksVibratorServicesTests",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
@@ -34,7 +32,6 @@
       "name": "CtsVibratorTestCases",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 80b7d40c14c513010c22a82413aa674e02ea2c3e..4c8ef97a7437a868b5f7a5dfb811e9f01a312b17 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2940,6 +2940,12 @@ public class UserManager {
      * Used to check if the context user is a restricted profile. Restricted profiles
      * may have a reduced number of available apps, app restrictions, and account restrictions.
      *
+     * <p>The caller must be in the same profile group as the context user or else hold
+     * <li>{@link android.Manifest.permission#MANAGE_USERS},
+     * <li>or {@link android.Manifest.permission#CREATE_USERS},
+     * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * {@link android.Manifest.permission#QUERY_USERS}.
+     *
      * @return whether the context user is a restricted profile.
      * @hide
      */
@@ -2963,6 +2969,12 @@ public class UserManager {
      * Check if a user is a restricted profile. Restricted profiles may have a reduced number of
      * available apps, app restrictions, and account restrictions.
      *
+     * <p>Requires
+     * <li>{@link android.Manifest.permission#MANAGE_USERS},
+     * <li>or {@link android.Manifest.permission#CREATE_USERS},
+     * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * {@link android.Manifest.permission#QUERY_USERS}.
+     *
      * @param user the user to check
      * @return whether the user is a restricted profile.
      * @hide
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 77be5d4008536f3f2638cc98c98290eeda18008c..1294f983b3b9368f0e7f06e04444d2cf27a9b173 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -6,3 +6,10 @@ flag {
   description: "enable device aware permission APIs"
   bug: "274852670"
 }
+
+flag {
+  name: "voice_activation_permission_apis"
+  namespace: "permissions"
+  description: "enable voice activation permission APIs"
+  bug: "287264308"
+}
\ No newline at end of file
diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
index 7b8d52f51cee7699a0ddb755fc0bb4f1113c1eae..468c4518602eea4c1dd61b8dc7a08967007592fc 100644
--- a/core/java/android/service/notification/TEST_MAPPING
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -3,9 +3,6 @@
     {
       "name": "CtsNotificationTestCases",
       "options": [
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
@@ -20,9 +17,6 @@
     {
       "name": "FrameworksUiServicesTests",
       "options": [
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING
index 0fe974afd15c572c3efcde7e5fc2ea677fec3789..c9bd2cacb138457710c1dbe29d2f7e5aab9d4fd1 100644
--- a/core/java/android/text/TEST_MAPPING
+++ b/core/java/android/text/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "CtsTextTestCases",
       "options": [
           {
-              "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+              "exclude-annotation": "androidx.test.filters.FlakyTest"
           },
           {
               "exclude-annotation": "androidx.test.filters.LargeTest"
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index d25c8a834c7bc2ef3eeaaa3c398d0ebd7f9678bb..7b7e34172fed4d129edc7a7a39aae73009dee3a9 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -25,9 +25,12 @@ import android.window.WindowContainerTransaction;
 interface ITaskFragmentOrganizerController {
 
     /**
-     * Registers a TaskFragmentOrganizer to manage TaskFragments.
+     * Registers a TaskFragmentOrganizer to manage TaskFragments. Registering a system
+     * organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer will have additional
+     * system capabilities.
      */
-    void registerOrganizer(in ITaskFragmentOrganizer organizer);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true)")
+    void registerOrganizer(in ITaskFragmentOrganizer organizer, in boolean isSystemOrganizer);
 
     /**
      * Unregisters a previously registered TaskFragmentOrganizer.
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index f785a3d1514e3a1b7a848d681c957714b5768919..a6c9cecb508fc5ca4f0838eff3bbbfbe396242eb 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -22,9 +22,11 @@ import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -32,6 +34,8 @@ import android.os.RemoteException;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 
+import com.android.window.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
@@ -140,12 +144,34 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
     }
 
     /**
-     * Registers a TaskFragmentOrganizer to manage TaskFragments.
+     * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
      */
     @CallSuper
     public void registerOrganizer() {
+        // TODO(b/302420256) point to registerOrganizer(boolean) when flag is removed.
+        try {
+            getController().registerOrganizer(mInterface, false /* isSystemOrganizer */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments.
+     *
+     * Registering a system organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer
+     * will have additional system capabilities, including: (1) it will receive SurfaceControl for
+     * the organized TaskFragment, and (2) it needs to update the
+     * {@link android.view.SurfaceControl} following the window change accordingly.
+     *
+     * @hide
+     */
+    @CallSuper
+    @RequiresPermission(value = "android.permission.MANAGE_ACTIVITY_TASKS", conditional = true)
+    @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG)
+    public void registerOrganizer(boolean isSystemOrganizer) {
         try {
-            getController().registerOrganizer(mInterface);
+            getController().registerOrganizer(mInterface, isSystemOrganizer);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 3c5d60dc8ae26b72791bb8da24fe7fd9f228137f..4dada108c4c64b1e4454c623f1e8b3c5499f233f 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.SurfaceControl;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -192,6 +193,9 @@ public final class TaskFragmentTransaction implements Parcelable {
         @Nullable
         private TaskFragmentParentInfo mTaskFragmentParentInfo;
 
+        @Nullable
+        private SurfaceControl mSurfaceControl;
+
         public Change(@ChangeType int type) {
             mType = type;
         }
@@ -206,6 +210,7 @@ public final class TaskFragmentTransaction implements Parcelable {
             mActivityIntent = in.readTypedObject(Intent.CREATOR);
             mActivityToken = in.readStrongBinder();
             mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
+            mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR);
         }
 
         @Override
@@ -219,6 +224,7 @@ public final class TaskFragmentTransaction implements Parcelable {
             dest.writeTypedObject(mActivityIntent, flags);
             dest.writeStrongBinder(mActivityToken);
             dest.writeTypedObject(mTaskFragmentParentInfo, flags);
+            dest.writeTypedObject(mSurfaceControl, flags);
         }
 
         /** The change is related to the TaskFragment created with this unique token. */
@@ -306,6 +312,13 @@ public final class TaskFragmentTransaction implements Parcelable {
             return this;
         }
 
+        /** @hide */
+        @NonNull
+        public Change setTaskFragmentSurfaceControl(@Nullable SurfaceControl sc) {
+            mSurfaceControl = sc;
+            return this;
+        }
+
         @ChangeType
         public int getType() {
             return mType;
@@ -359,6 +372,21 @@ public final class TaskFragmentTransaction implements Parcelable {
             return mTaskFragmentParentInfo;
         }
 
+        /**
+         * Gets the {@link SurfaceControl} of the TaskFragment. This field is {@code null} for
+         * a regular {@link TaskFragmentOrganizer} and is only available for a system
+         * {@link TaskFragmentOrganizer} in the
+         * {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_APPEARED} event. See
+         * {@link ITaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer,
+         * boolean)}
+         *
+         * @hide
+         */
+        @Nullable
+        public SurfaceControl getTaskFragmentSurfaceControl() {
+            return mSurfaceControl;
+        }
+
         @Override
         public String toString() {
             return "Change{ type=" + mType + " }";
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 7a87c3a82e43809cc1f1ff309275d94e31ddef3c..d503904c2e3ca6f7036eeda4a9768b727851197d 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -16,11 +16,9 @@
 
 package com.android.internal.display;
 
-import android.annotation.SuppressLint;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
-import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
@@ -56,7 +54,8 @@ public class BrightnessSynchronizer {
     private static final int MSG_RUN_UPDATE = 1;
 
     // The tolerance within which we consider brightness values approximately equal to eachother.
-    public static final float EPSILON = 0.0001f;
+    // This value is approximately 1/3 of the smallest possible brightness value.
+    public static final float EPSILON = 0.001f;
 
     private static int sBrightnessUpdateCount = 1;
 
@@ -284,74 +283,6 @@ public class BrightnessSynchronizer {
         }
     }
 
-    /**
-     * Converts between the int brightness setting and the float brightness system. The int
-     * brightness setting is between 0-255 and matches the brightness slider - e.g. 128 is 50% on
-     * the slider. Accounts for special values such as OFF and invalid values. Accounts for
-     * brightness limits; the maximum value here represents the max value allowed on the slider.
-     */
-    @VisibleForTesting
-    @SuppressLint("AndroidFrameworkRequiresPermission")
-    public float brightnessIntSettingToFloat(int brightnessInt) {
-        if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
-            return PowerManager.BRIGHTNESS_OFF_FLOAT;
-        } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
-            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        } else {
-            final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
-            final float maxInt = PowerManager.BRIGHTNESS_ON;
-
-            // Normalize to the range [0, 1]
-            float userPerceptionBrightness = MathUtils.norm(minInt, maxInt, brightnessInt);
-
-            // Convert from user-perception to linear scale
-            float linearBrightness = BrightnessUtils.convertGammaToLinear(userPerceptionBrightness);
-
-            // Interpolate to the range [0, currentlyAllowedMax]
-            final Display display = mContext.getDisplay();
-            if (display == null) {
-                return PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            }
-            final BrightnessInfo info = display.getBrightnessInfo();
-            return MathUtils.lerp(info.brightnessMinimum, info.brightnessMaximum, linearBrightness);
-        }
-    }
-
-    /**
-     * Translates specified value from the float brightness system to the setting int brightness
-     * system. The value returned is between 0-255 and matches the brightness slider - e.g. 128 is
-     * 50% on the slider. Accounts for special values such as OFF and invalid values. Accounts for
-     * brightness limits; the maximum value here represents the max value currently allowed on
-     * the slider.
-     */
-    @VisibleForTesting
-    @SuppressLint("AndroidFrameworkRequiresPermission")
-    public int brightnessFloatToIntSetting(float brightnessFloat) {
-        if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
-            return PowerManager.BRIGHTNESS_OFF;
-        } else if (Float.isNaN(brightnessFloat)) {
-            return PowerManager.BRIGHTNESS_INVALID;
-        } else {
-            // Normalize to the range [0, 1]
-            final Display display = mContext.getDisplay();
-            if (display == null) {
-                return PowerManager.BRIGHTNESS_INVALID;
-            }
-            final BrightnessInfo info = display.getBrightnessInfo();
-            float linearBrightness =
-                    MathUtils.norm(info.brightnessMinimum, info.brightnessMaximum, brightnessFloat);
-
-            // Convert from linear to user-perception scale
-            float userPerceptionBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
-
-            // Interpolate to the range [0, 255]
-            final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
-            final float maxInt = PowerManager.BRIGHTNESS_ON;
-            float intBrightness = MathUtils.lerp(minInt, maxInt, userPerceptionBrightness);
-            return Math.round(intBrightness);
-        }
-    }
-
     /**
      * Encapsulates a brightness change event and contains logic for synchronizing the appropriate
      * settings for the specified brightness change.
@@ -490,14 +421,14 @@ public class BrightnessSynchronizer {
             if (mSourceType == TYPE_INT) {
                 return (int) mBrightness;
             }
-            return brightnessFloatToIntSetting(mBrightness);
+            return brightnessFloatToInt(mBrightness);
         }
 
         private float getBrightnessAsFloat() {
             if (mSourceType == TYPE_FLOAT) {
                 return mBrightness;
             }
-            return brightnessIntSettingToFloat((int) mBrightness);
+            return brightnessIntToFloat((int) mBrightness);
         }
 
         private String toStringLabel(int type, float brightness) {
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index ddfd0ed18bef7b7fe16eb0a379f6e0c7bc052010..c09181f2f49640b9c1ec803c19b287607ebb962d 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "CtsRoleTestCases",
       "options": [
           {
-              "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+              "exclude-annotation": "androidx.test.filters.FlakyTest"
           }
       ]
     },
@@ -15,7 +15,7 @@
             "include-filter": "android.permission.cts.PermissionControllerTest"
           },
           {
-            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
           }
       ]
     },
diff --git a/core/java/com/android/internal/net/OWNERS b/core/java/com/android/internal/net/OWNERS
index 71f997bb57c58c25229476476f4b2915fd125c8b..71576837c2e12cb647a85fde601d7a4cd6f10b65 100644
--- a/core/java/com/android/internal/net/OWNERS
+++ b/core/java/com/android/internal/net/OWNERS
@@ -1,4 +1,4 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
 
 jsharkey@android.com
diff --git a/core/java/com/android/server/net/OWNERS b/core/java/com/android/server/net/OWNERS
index 62c5737a2e8e5a7da8dcff019eb36d3c55b94ce4..c24680e9b06a616db26148e4d2ce2e833626daf9 100644
--- a/core/java/com/android/server/net/OWNERS
+++ b/core/java/com/android/server/net/OWNERS
@@ -1,2 +1,2 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c00a776fc3da1749749f69eda3527948baf6a769..4e073ca3863e4c6fb839b4177ef84391c225c25a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4830,7 +4830,7 @@
     <string translatable="false" name="config_deviceSpecificInputMethodManagerService"></string>
 
     <!-- Component name of media projection permission dialog -->
-    <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
+    <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity</string>
 
     <!-- Corner radius of system dialogs -->
     <dimen name="config_dialogCornerRadius">28dp</dimen>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 878e6b306571427241095957744f20dbd0c96248..71d696ea45548ed40f9c9d9de4c04fedfd9fb509 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -172,28 +172,17 @@
     <integer name="config_satellite_nb_iot_inactivity_timeout_millis">180000</integer>
     <java-symbol type="integer" name="config_satellite_nb_iot_inactivity_timeout_millis" />
 
-    <!-- Telephony config for services supported by satellite providers. The format of each config
-         string in the array is as follows: "PLMN_1:service_1,service_2,..."
-         where PLMN is the satellite PLMN of a provider and service is an integer with the
-         following value:
-            1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}
-            2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}
-            3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
-            4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
-            5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
-            6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS}
-         Example of a config string: "10011:2,3"
-
-         The PLMNs not configured in this array will be ignored and will not be used for satellite
-         scanning. -->
-    <string-array name="config_satellite_services_supported_by_providers" translatable="false">
-    </string-array>
-    <java-symbol type="array" name="config_satellite_services_supported_by_providers" />
+    <!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
+         to identify providers that should be ignored if the carrier config
+         carrier_supported_satellite_services_per_provider_bundle does not support them.
+         -->
+    <string-array name="config_satellite_providers" translatable="false"></string-array>
+    <java-symbol type="array" name="config_satellite_providers" />
 
-    <!-- The identifier of the satellite's eSIM profile preloaded on the device. The identifier is
-    composed of MCC and MNC of the satellite PLMN with the format "mccmnc". -->
-    <string name="config_satellite_esim_identifier" translatable="false"></string>
-    <java-symbol type="string" name="config_satellite_esim_identifier" />
+    <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
+         of the satellite PLMN with the format "mccmnc". -->
+    <string name="config_satellite_sim_identifier" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_sim_identifier" />
 
     <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
          will not perform handover if the target transport is out of service, or VoPS not
diff --git a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ecd75a8370ce44ff9fe252b44a38ca7bf8b0cf64
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link DestroyActivityItem}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:DestroyActivityItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class DestroyActivityItemTest {
+
+    @Mock
+    private ClientTransactionHandler mHandler;
+    @Mock
+    private PendingTransactionActions mPendingActions;
+    @Mock
+    private IBinder mActivityToken;
+
+    // Can't mock final class.
+    private ActivityClientRecord mActivityClientRecord;
+
+    private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed;
+    private DestroyActivityItem mItem;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mItem = DestroyActivityItem.obtain(
+                mActivityToken, false /* finished */, 123 /* configChanges */);
+        mActivityClientRecord = new ActivityClientRecord();
+        mActivitiesToBeDestroyed = new ArrayMap<>();
+
+        doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed();
+    }
+
+    @Test
+    public void testPreExecute() {
+        mItem.preExecute(mHandler);
+
+        assertEquals(1, mActivitiesToBeDestroyed.size());
+        assertEquals(mItem, mActivitiesToBeDestroyed.get(mActivityToken));
+    }
+
+    @Test
+    public void testPostExecute() {
+        mItem.preExecute(mHandler);
+        mItem.postExecute(mHandler, mPendingActions);
+
+        assertTrue(mActivitiesToBeDestroyed.isEmpty());
+    }
+
+    @Test
+    public void testExecute() {
+        mItem.execute(mHandler, mActivityClientRecord, mPendingActions);
+
+        verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
+                eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index a1a2bdbe0f151d07acf7806350ac638c102cfe59..44a4d580dbc0339939d6be5056dbd83194207c0c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -28,6 +28,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
@@ -247,7 +248,7 @@ public class TransactionExecutorTests {
 
     @Test
     public void testDoNotLaunchDestroyedActivity() {
-        final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed = new ArrayMap<>();
+        final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
         when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
         // Assume launch transaction is still in queue, so there is no client record.
         when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
@@ -259,7 +260,7 @@ public class TransactionExecutorTests {
                 DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
         destroyTransaction.preExecute(mTransactionHandler);
         // The activity should be added to to-be-destroyed container.
-        assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size());
+        assertEquals(1, activitiesToBeDestroyed.size());
 
         // A previous queued launch transaction runs on main thread (execute).
         final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
@@ -274,7 +275,7 @@ public class TransactionExecutorTests {
 
         // After the destroy transaction has been executed, the token should be removed.
         mExecutor.execute(destroyTransaction);
-        assertEquals(0, mTransactionHandler.getActivitiesToBeDestroyed().size());
+        assertTrue(activitiesToBeDestroyed.isEmpty());
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a8b40325a71381c8edf12bfa88be0a01184021f6..a5bbeb58bc0894c8f23fa6fd24168cc87340ecc9 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -17,6 +17,7 @@
 package android.window.flags;
 
 import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
 
 import android.platform.test.annotations.Presubmit;
 
@@ -42,4 +43,10 @@ public class WindowFlagsTest {
         // No crash when accessing the flag.
         syncWindowConfigUpdateFlag();
     }
+
+    @Test
+    public void testTaskFragmentSystemOrganizerFlag() {
+        // No crash when accessing the flag.
+        taskFragmentSystemOrganizerFlag();
+    }
 }
diff --git a/core/tests/vibrator/TEST_MAPPING b/core/tests/vibrator/TEST_MAPPING
index 2f3afa6f63996a9b5ff99ecc54f149124495ad13..54a5ff1d675d0b22f5acb14ef37b7d5eb4fb33b6 100644
--- a/core/tests/vibrator/TEST_MAPPING
+++ b/core/tests/vibrator/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "FrameworksVibratorCoreTests",
       "options": [
         {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3d825f072a6aa38237661c72b79db0fc9dcd5698..4ea14f473c390376f3805ae0881c640189379596 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -720,6 +720,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
         mSplitLayout.setDivideRatio(snapPosition);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
+        wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                false /* reparentLeafTaskIfRelaunch */);
         setRootForceTranslucent(false, wct);
 
         // Make sure the launch options will put tasks in the corresponding split roots
@@ -767,6 +769,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
         mSplitLayout.setDivideRatio(snapPosition);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
+        wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+                false /* reparentLeafTaskIfRelaunch */);
         setRootForceTranslucent(false, wct);
 
         options1 = options1 != null ? options1 : new Bundle();
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7ee247a9d11899b3a69f406f4441a99a25..0fffee744be0564ffbb0b6686d3bb06805249010 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@ static void Gainmap_writeToParcel(JNIEnv* env, jobject, jlong nativeObject, jobj
     p.writeFloat(info.fDisplayRatioHdr);
     // base image type
     p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
-    // type
-    p.writeInt32(static_cast<int32_t>(info.fType));
 #else
     doThrowRE(env, "Cannot use parcels outside of Android!");
 #endif
@@ -232,7 +230,6 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job
     info.fDisplayRatioSdr = p.readFloat();
     info.fDisplayRatioHdr = p.readFloat();
     info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
-    info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
 
     fromJava(nativeObject)->info = info;
 #else
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index b0cdb0554c11b3bb500fb47e4603647453cf23b4..230fb07f9f43ce4fd4e36328dc51ed35e001a2d3 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1167,7 +1167,10 @@ public final class AudioAttributes implements Parcelable {
                                 streamType);
                 if (attributes != null) {
                     mUsage = attributes.mUsage;
-                    mContentType = attributes.mContentType;
+                    // on purpose ignoring the content type: stream types are deprecated for
+                    // playback, making assumptions about the content type is prone to
+                    // interpretation errors for ambiguous types such as STREAM_TTS and STREAM_MUSIC
+                    //mContentType = attributes.mContentType;
                     mFlags = attributes.getAllFlags();
                     mMuteHapticChannels = attributes.areHapticChannelsMuted();
                     mIsContentSpatialized = attributes.isContentSpatialized();
@@ -1177,49 +1180,47 @@ public final class AudioAttributes implements Parcelable {
                     mSource = attributes.mSource;
                 }
             }
-            if (mContentType == CONTENT_TYPE_UNKNOWN) {
-                switch (streamType) {
-                    case AudioSystem.STREAM_VOICE_CALL:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        break;
-                    case AudioSystem.STREAM_SYSTEM_ENFORCED:
-                        mFlags |= FLAG_AUDIBILITY_ENFORCED;
-                        // intended fall through, attributes in common with STREAM_SYSTEM
-                    case AudioSystem.STREAM_SYSTEM:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_RING:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_MUSIC:
-                        mContentType = CONTENT_TYPE_MUSIC;
-                        break;
-                    case AudioSystem.STREAM_ALARM:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_NOTIFICATION:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_BLUETOOTH_SCO:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        mFlags |= FLAG_SCO;
-                        break;
-                    case AudioSystem.STREAM_DTMF:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        break;
-                    case AudioSystem.STREAM_TTS:
-                        mContentType = CONTENT_TYPE_SONIFICATION;
-                        mFlags |= FLAG_BEACON;
-                        break;
-                    case AudioSystem.STREAM_ACCESSIBILITY:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        break;
-                    case AudioSystem.STREAM_ASSISTANT:
-                        mContentType = CONTENT_TYPE_SPEECH;
-                        break;
-                    default:
-                        Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
-                }
+            switch (streamType) {
+                case AudioSystem.STREAM_VOICE_CALL:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    break;
+                case AudioSystem.STREAM_SYSTEM_ENFORCED:
+                    mFlags |= FLAG_AUDIBILITY_ENFORCED;
+                    // intended fall through, attributes in common with STREAM_SYSTEM
+                case AudioSystem.STREAM_SYSTEM:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_RING:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_ALARM:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_NOTIFICATION:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_BLUETOOTH_SCO:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    mFlags |= FLAG_SCO;
+                    break;
+                case AudioSystem.STREAM_DTMF:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_TTS:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    mFlags |= FLAG_BEACON;
+                    break;
+                case AudioSystem.STREAM_ACCESSIBILITY:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    break;
+                case AudioSystem.STREAM_ASSISTANT:
+                    mContentType = CONTENT_TYPE_SPEECH;
+                    break;
+                case AudioSystem.STREAM_MUSIC:
+                    // leaving as CONTENT_TYPE_UNKNOWN
+                    break;
+                default:
+                    Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
             }
             if (mUsage == USAGE_UNKNOWN) {
                 mUsage = usageForStreamType(streamType);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f9ea7735681f4f837650b35022b251a6641bd647..49bd9d9940886df6178707d911c824ab2c745610 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -345,6 +345,8 @@
     <string name="accessibility_wifi_three_bars">Wifi three bars.</string>
     <!-- Content description of the WIFI signal when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_wifi_signal_full">Wifi signal full.</string>
+    <!-- Content description of the WIFI signal when the WIFI is connected using the signal from a different device owned by the user. For accessibility (not shown on the screen) [CHAR LIMIT=NONE] -->
+    <string name="accessibility_wifi_other_device">Connected to your device.</string>
 
     <!-- Content description of the Wi-Fi security type. This message indicates this is an open Wi-Fi (no password needed) [CHAR LIMIT=NONE] -->
     <string name="accessibility_wifi_security_type_none">Open network</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index ee65ef4e92b63a44d50edc225236a2b7efcdf819..ce466dfbf19c7ceaced4da4c47dabfbe7a570576 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -50,6 +50,7 @@ public class AccessibilityContentDescriptions {
     };
 
     public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
+    public static final int WIFI_OTHER_DEVICE_CONNECTION = R.string.accessibility_wifi_other_device;
 
     public static final int NO_CALLING = R.string.accessibility_no_calling;
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b5b873c8231fd45ad925d4e027334a3802563989..9bfc4be0f30d7e5fedf8f6f1d2bc18c00019352d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -634,7 +634,7 @@
 
         <!-- started from MediaProjectionManager -->
         <activity
-            android:name=".media.MediaProjectionPermissionActivity"
+            android:name=".mediaprojection.permission.MediaProjectionPermissionActivity"
             android:exported="true"
             android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog"
             android:finishOnCloseSystemDialogs="true"
@@ -643,7 +643,7 @@
             android:visibleToInstantApps="true"/>
 
         <activity
-            android:name=".media.MediaProjectionAppSelectorActivity"
+            android:name=".mediaprojection.appselector.MediaProjectionAppSelectorActivity"
             android:theme="@style/Theme.SystemUI.MediaProjectionAppSelector"
             android:finishOnCloseSystemDialogs="true"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 510fa1e1fa803020cb2688aba50bde3ab1999096..42d088f218a1d4511208e02e9509ba01f10e4c3f 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -34,7 +34,9 @@ android_library {
 
         "androidx.compose.runtime_runtime",
         "androidx.compose.material3_material3",
+        "androidx.compose.material3_material3-window-size-class",
         "androidx.savedstate_savedstate",
+        "androidx.window_window",
     ],
 
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
index 0cc259ab7015d7aaed4c8c44bab36ebbc46a7abe..a62c9840add189be7e23fd92bec29acf898d3df6 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
@@ -151,7 +151,7 @@ internal fun Modifier.element(
                 element.lastAlpha = alpha
             }
         }
-        .testTag(key.name)
+        .testTag(key.testTag)
 }
 
 private fun shouldDrawElement(
@@ -167,7 +167,8 @@ private fun shouldDrawElement(
             state.fromScene == state.toScene ||
             !layoutImpl.isTransitionReady(state) ||
             state.fromScene !in element.sceneValues ||
-            state.toScene !in element.sceneValues
+            state.toScene !in element.sceneValues ||
+            !isSharedElementEnabled(layoutImpl, state, element.key)
     ) {
         return true
     }
@@ -191,6 +192,26 @@ private fun shouldDrawElement(
     }
 }
 
+private fun isSharedElementEnabled(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: TransitionState.Transition,
+    element: ElementKey,
+): Boolean {
+    val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
+    val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
+    val sharedInToScene = spec.transformations(element, transition.toScene).shared
+
+    // The sharedElement() transformation must either be null or be the same in both scenes.
+    if (sharedInFromScene != sharedInToScene) {
+        error(
+            "Different sharedElement() transformations matched $element (from=$sharedInFromScene " +
+                "to=$sharedInToScene)"
+        )
+    }
+
+    return sharedInFromScene?.enabled ?: true
+}
+
 /**
  * Chain the [com.android.compose.animation.scene.transformation.ModifierTransformation] applied
  * throughout the current transition, if any.
@@ -213,7 +234,7 @@ private fun Modifier.modifierTransformations(
 
             return layoutImpl.transitions
                 .transitionSpec(fromScene, state.toScene)
-                .transformations(element.key)
+                .transformations(element.key, scene.key)
                 .modifier
                 .fold(this) { modifier, transformation ->
                     with(transformation) {
@@ -407,17 +428,20 @@ private inline fun <T> computeValue(
     // The element is shared: interpolate between the value in fromScene and the value in toScene.
     // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
     // elements follow the finger direction.
-    if (fromValues != null && toValues != null) {
+    val isSharedElement = fromValues != null && toValues != null
+    if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
         return lerp(
-            sceneValue(fromValues),
-            sceneValue(toValues),
+            sceneValue(fromValues!!),
+            sceneValue(toValues!!),
             transitionProgress,
         )
     }
 
     val transformation =
         transformation(
-            layoutImpl.transitions.transitionSpec(fromScene, toScene).transformations(element.key)
+            layoutImpl.transitions
+                .transitionSpec(fromScene, toScene)
+                .transformations(element.key, scene.key)
         )
         // If there is no transformation explicitly associated to this element value, let's use
         // the value given by the system (like the current position and size given by the layout
@@ -426,12 +450,21 @@ private inline fun <T> computeValue(
 
     // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
     // end (for leaving elements) of the transition.
+    val sceneValues =
+        checkNotNull(
+            when {
+                isSharedElement && scene.key == fromScene -> fromValues
+                isSharedElement -> toValues
+                else -> fromValues ?: toValues
+            }
+        )
+
     val targetValue =
         transformation.transform(
             layoutImpl,
             scene,
             element,
-            fromValues ?: toValues!!,
+            sceneValues,
             state,
             idleValue,
         )
@@ -440,7 +473,7 @@ private inline fun <T> computeValue(
     val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress
 
     // Interpolate between the value at rest and the value before entering/after leaving.
-    val isEntering = fromValues == null
+    val isEntering = scene.key == toScene
     return if (isEntering) {
         lerp(targetValue, idleValue, rangeProgress)
     } else {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
new file mode 100644
index 0000000000000000000000000000000000000000..98dbb67d7c6694a7b5e539eb4d61021425d4afd1
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.compose.animation.scene
+
+/** An interface to match one or more elements. */
+interface ElementMatcher {
+    /** Whether the element with key [key] in scene [scene] matches this matcher. */
+    fun matches(key: ElementKey, scene: SceneKey): Boolean
+}
+
+/**
+ * Returns an [ElementMatcher] that matches elements in [scene] also matching [this]
+ * [ElementMatcher].
+ */
+fun ElementMatcher.inScene(scene: SceneKey): ElementMatcher {
+    val delegate = this
+    val matcherScene = scene
+    return object : ElementMatcher {
+        override fun matches(key: ElementKey, scene: SceneKey): Boolean {
+            return scene == matcherScene && delegate.matches(key, scene)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
index f7ebe2fc6d343c2f1f78f6451c497000fca618ed..b7acc48e286500c9c2b3e0dd4b8cd65bb58bc5bf 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -16,6 +16,8 @@
 
 package com.android.compose.animation.scene
 
+import androidx.annotation.VisibleForTesting
+
 /**
  * A base class to create unique keys, associated to an [identity] that is used to check the
  * equality of two key instances.
@@ -41,6 +43,7 @@ class SceneKey(
     name: String,
     identity: Any = Object(),
 ) : Key(name, identity) {
+    @VisibleForTesting val testTag: String = "scene:$name"
 
     /** The unique [ElementKey] identifying this scene's root element. */
     val rootElementKey = ElementKey(name, identity)
@@ -61,7 +64,9 @@ class ElementKey(
      */
     val isBackground: Boolean = false,
 ) : Key(name, identity), ElementMatcher {
-    override fun matches(key: ElementKey): Boolean {
+    @VisibleForTesting val testTag: String = "element:$name"
+
+    override fun matches(key: ElementKey, scene: SceneKey): Boolean {
         return key == this
     }
 
@@ -73,7 +78,9 @@ class ElementKey(
         /** Matches any element whose [key identity][ElementKey.identity] matches [predicate]. */
         fun withIdentity(predicate: (Any) -> Boolean): ElementMatcher {
             return object : ElementMatcher {
-                override fun matches(key: ElementKey): Boolean = predicate(key.identity)
+                override fun matches(key: ElementKey, scene: SceneKey): Boolean {
+                    return predicate(key.identity)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
index b44c8efc7ee21fc07b12aa2e1885ee3fec6feeb6..3985233bd19779e49763b6f4da71028443122e04 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
@@ -25,6 +25,7 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.zIndex
 
@@ -45,7 +46,9 @@ internal class Scene(
 
     @Composable
     fun Content(modifier: Modifier = Modifier) {
-        Box(modifier.zIndex(zIndex).onPlaced { size = it.size }) { scope.content() }
+        Box(modifier.zIndex(zIndex).onPlaced { size = it.size }.testTag(key.testTag)) {
+            scope.content()
+        }
     }
 
     override fun toString(): String {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
index f4e39023edfe9c9404302e5f14558b7980c0605c..75dcb2e44c135befcff683741a796cdde1491ad0 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene
 
+import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.snap
 import androidx.compose.ui.geometry.Offset
@@ -28,6 +29,7 @@ import com.android.compose.animation.scene.transformation.ModifierTransformation
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.Translate
 import com.android.compose.ui.util.fastForEach
@@ -35,11 +37,12 @@ import com.android.compose.ui.util.fastMap
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions(
-    private val transitionSpecs: List<TransitionSpec>,
+    @get:VisibleForTesting val transitionSpecs: List<TransitionSpec>,
 ) {
     private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
 
-    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+    @VisibleForTesting
+    fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
         return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
     }
 
@@ -97,7 +100,8 @@ data class TransitionSpec(
     val transformations: List<Transformation>,
     val spec: AnimationSpec<Float>,
 ) {
-    private val cache = mutableMapOf<ElementKey, ElementTransformations>()
+    // TODO(b/302300957): Make sure this cache does not infinitely grow.
+    private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
 
     internal fun reverse(): TransitionSpec {
         return copy(
@@ -107,12 +111,18 @@ data class TransitionSpec(
         )
     }
 
-    internal fun transformations(element: ElementKey): ElementTransformations {
-        return cache.getOrPut(element) { computeTransformations(element) }
+    internal fun transformations(element: ElementKey, scene: SceneKey): ElementTransformations {
+        return cache
+            .getOrPut(element) { mutableMapOf() }
+            .getOrPut(scene) { computeTransformations(element, scene) }
     }
 
     /** Filter [transformations] to compute the [ElementTransformations] of [element]. */
-    private fun computeTransformations(element: ElementKey): ElementTransformations {
+    private fun computeTransformations(
+        element: ElementKey,
+        scene: SceneKey,
+    ): ElementTransformations {
+        var shared: SharedElementTransformation? = null
         val modifier = mutableListOf<ModifierTransformation>()
         var offset: PropertyTransformation<Offset>? = null
         var size: PropertyTransformation<IntSize>? = null
@@ -126,16 +136,16 @@ data class TransitionSpec(
                 is Translate,
                 is EdgeTranslate,
                 is AnchoredTranslate -> {
-                    throwIfNotNull(offset, element, property = "offset")
+                    throwIfNotNull(offset, element, name = "offset")
                     offset = root as PropertyTransformation<Offset>
                 }
                 is ScaleSize,
                 is AnchoredSize -> {
-                    throwIfNotNull(size, element, property = "size")
+                    throwIfNotNull(size, element, name = "size")
                     size = root as PropertyTransformation<IntSize>
                 }
                 is Fade -> {
-                    throwIfNotNull(alpha, element, property = "alpha")
+                    throwIfNotNull(alpha, element, name = "alpha")
                     alpha = root as PropertyTransformation<Float>
                 }
                 is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate)
@@ -143,32 +153,37 @@ data class TransitionSpec(
         }
 
         transformations.fastForEach { transformation ->
-            if (!transformation.matcher.matches(element)) {
+            if (!transformation.matcher.matches(element, scene)) {
                 return@fastForEach
             }
 
             when (transformation) {
+                is SharedElementTransformation -> {
+                    throwIfNotNull(shared, element, name = "shared")
+                    shared = transformation
+                }
                 is ModifierTransformation -> modifier.add(transformation)
                 is PropertyTransformation<*> -> onPropertyTransformation(transformation)
             }
         }
 
-        return ElementTransformations(modifier, offset, size, alpha)
+        return ElementTransformations(shared, modifier, offset, size, alpha)
     }
 
     private fun throwIfNotNull(
-        previous: PropertyTransformation<*>?,
+        previous: Transformation?,
         element: ElementKey,
-        property: String,
+        name: String,
     ) {
         if (previous != null) {
-            error("$element has multiple transformations for its $property property")
+            error("$element has multiple $name transformations")
         }
     }
 }
 
 /** The transformations of an element during a transition. */
 internal class ElementTransformations(
+    val shared: SharedElementTransformation?,
     val modifier: List<ModifierTransformation>,
     val offset: PropertyTransformation<Offset>?,
     val size: PropertyTransformation<IntSize>?,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
index fb12b90d7d3e928716581697ec7f248acb57ecd7..49669775fedd7d4d2ae7088055ef88b4d78994fb 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -115,6 +115,14 @@ interface TransitionBuilder : PropertyTransformationBuilder {
         builder: PropertyTransformationBuilder.() -> Unit,
     )
 
+    /**
+     * Configure the shared transition when [matcher] is shared between two scenes.
+     *
+     * @param enabled whether the matched element(s) should actually be shared in this transition.
+     *   Defaults to true.
+     */
+    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
+
     /**
      * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
      * using the given [shape].
@@ -127,6 +135,13 @@ interface TransitionBuilder : PropertyTransformationBuilder {
      * the result.
      */
     fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape = RectangleShape)
+
+    /**
+     * Adds the transformations in [builder] but in reversed order. This allows you to partially
+     * reuse the definition of the transition from scene `Foo` to scene `Bar` inside the definition
+     * of the transition from scene `Bar` to scene `Foo`.
+     */
+    fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
 @TransitionDsl
@@ -179,12 +194,6 @@ interface PropertyTransformationBuilder {
     fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey)
 }
 
-/** An interface to match one or more elements. */
-interface ElementMatcher {
-    /** Whether the element with key [key] matches this matcher. */
-    fun matches(key: ElementKey): Boolean
-}
-
 /** The edge of a [SceneTransitionLayout]. */
 enum class Edge {
     Left,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 48d5638e8b4ef86b2d967158dedc3c723210dec1..f1c27178391c67bd0eef2ee44bcef3e4952afd84 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@ import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.PunchHole
 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.TransformationRange
 import com.android.compose.animation.scene.transformation.Translate
@@ -80,6 +81,7 @@ internal class TransitionBuilderImpl : TransitionBuilder {
     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
 
     private var range: TransformationRange? = null
+    private var reversed = false
     private val durationMillis: Int by lazy {
         val spec = spec
         if (spec !is DurationBasedAnimationSpec) {
@@ -93,6 +95,12 @@ internal class TransitionBuilderImpl : TransitionBuilder {
         transformations.add(PunchHole(matcher, bounds, shape))
     }
 
+    override fun reversed(builder: TransitionBuilder.() -> Unit) {
+        reversed = true
+        builder()
+        reversed = false
+    }
+
     override fun fractionRange(
         start: Float?,
         end: Float?,
@@ -103,6 +111,10 @@ internal class TransitionBuilderImpl : TransitionBuilder {
         range = null
     }
 
+    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
+        transformations.add(SharedElementTransformation(matcher, enabled))
+    }
+
     override fun timestampRange(
         startMillis: Int?,
         endMillis: Int?,
@@ -122,11 +134,20 @@ internal class TransitionBuilderImpl : TransitionBuilder {
     }
 
     private fun transformation(transformation: PropertyTransformation<*>) {
-        if (range != null) {
-            transformations.add(RangedPropertyTransformation(transformation, range!!))
-        } else {
-            transformations.add(transformation)
-        }
+        val transformation =
+            if (range != null) {
+                RangedPropertyTransformation(transformation, range!!)
+            } else {
+                transformation
+            }
+
+        transformations.add(
+            if (reversed) {
+                transformation.reverse()
+            } else {
+                transformation
+            }
+        )
     }
 
     override fun fade(matcher: ElementMatcher) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
index ce6749da271103e45d9a254ecb6cb5bbf237d9cf..a65025423aee55fd0fe6a29565f05b388dc8ea41 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -30,6 +30,14 @@ sealed interface Transformation {
      */
     val matcher: ElementMatcher
 
+    /**
+     * The range during which the transformation is applied. If it is `null`, then the
+     * transformation will be applied throughout the whole scene transition.
+     */
+    // TODO(b/240432457): Move this back to PropertyTransformation.
+    val range: TransformationRange?
+        get() = null
+
     /*
      * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
      * animating from B to A and there is no Transition(from = B, to = A) defined.
@@ -37,6 +45,11 @@ sealed interface Transformation {
     fun reverse(): Transformation = this
 }
 
+internal class SharedElementTransformation(
+    override val matcher: ElementMatcher,
+    internal val enabled: Boolean,
+) : Transformation
+
 /** A transformation that is applied on the element during the whole transition. */
 internal interface ModifierTransformation : Transformation {
     /** Apply the transformation to [element]. */
@@ -52,13 +65,6 @@ internal interface ModifierTransformation : Transformation {
 
 /** A transformation that changes the value of an element property, like its size or offset. */
 internal sealed interface PropertyTransformation<T> : Transformation {
-    /**
-     * The range during which the transformation is applied. If it is `null`, then the
-     * transformation will be applied throughout the whole scene transition.
-     */
-    val range: TransformationRange?
-        get() = null
-
     /**
      * Transform [value], i.e. the value of the transformed property without this transformation.
      */
@@ -92,8 +98,7 @@ internal class RangedPropertyTransformation<T>(
 }
 
 /** The progress-based range of a [PropertyTransformation]. */
-data class TransformationRange
-private constructor(
+data class TransformationRange(
     val start: Float,
     val end: Float,
 ) {
@@ -133,6 +138,6 @@ private constructor(
     }
 
     companion object {
-        private const val BoundUnspecified = Float.MIN_VALUE
+        const val BoundUnspecified = Float.MIN_VALUE
     }
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
index b4e90d63c6b8f2cec06aee72a890fd2203b92ae3..06618704e085089a7d54c24213222b849f4649a8 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
@@ -29,6 +29,8 @@ import com.android.compose.theme.typography.TypefaceNames
 import com.android.compose.theme.typography.TypefaceTokens
 import com.android.compose.theme.typography.TypographyTokens
 import com.android.compose.theme.typography.platformTypography
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.compose.windowsizeclass.calculateWindowSizeClass
 
 /** The Material 3 theme that should wrap all Platform Composables. */
 @Composable
@@ -51,10 +53,12 @@ fun PlatformTheme(
         remember(typefaceNames) {
             platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
         }
+    val windowSizeClass = calculateWindowSizeClass()
 
     MaterialTheme(colorScheme, typography = typography) {
         CompositionLocalProvider(
             LocalAndroidColorScheme provides androidColorScheme,
+            LocalWindowSizeClass provides windowSizeClass,
         ) {
             content()
         }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4674d6e5f25a720335ab74d1f817de88410a87df
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.compose.windowsizeclass
+
+import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
+import androidx.compose.material3.windowsizeclass.WindowSizeClass
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.window.layout.WindowMetricsCalculator
+
+val LocalWindowSizeClass =
+    staticCompositionLocalOf<WindowSizeClass> {
+        throw IllegalStateException(
+            "No WindowSizeClass configured. Make sure to use LocalWindowSizeClass in a Composable" +
+                " surrounded by a PlatformTheme {}."
+        )
+    }
+
+@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
+@Composable
+fun calculateWindowSizeClass(): WindowSizeClass {
+    // Observe view configuration changes and recalculate the size class on each change.
+    LocalConfiguration.current
+    val density = LocalDensity.current
+    val context = LocalContext.current
+    val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+    val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
+    return WindowSizeClass.calculateFromSize(size)
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 8bd654585f29bf78a9a82bf83a0704245d1e9903..328866ea76cab165266d153d7d12aa6846312c72 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -224,7 +224,7 @@ class SceneTransitionLayoutTest {
 
         // In scene A, the shared element SharedFoo() is at the top end of the layout and has a size
         // of 50.dp.
-        var sharedFoo = rule.onNodeWithTag(TestElements.Foo.name, useUnmergedTree = true)
+        var sharedFoo = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         sharedFoo.assertWidthIsEqualTo(50.dp)
         sharedFoo.assertHeightIsEqualTo(50.dp)
         sharedFoo.assertPositionInRootIsEqualTo(
@@ -250,7 +250,7 @@ class SceneTransitionLayoutTest {
 
         // We need to use onAllNodesWithTag().onFirst() here given that shared elements are
         // composed and laid out in both scenes (but drawn only in one).
-        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
 
         // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
         // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -284,7 +284,7 @@ class SceneTransitionLayoutTest {
         val expectedLeft = 0.dp
         val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
 
-        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
         assertThat((layoutState.transitionState as TransitionState.Transition).progress)
             .isEqualTo(interpolatedProgress)
         sharedFoo.assertWidthIsEqualTo(expectedSize)
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
index 275149a05abfad88d258f72f919c1692473e40b5..268057fd2f2c1132232ee940a7059b81741a709c 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
@@ -23,7 +23,11 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionCollection
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.onAllNodesWithTag
 import androidx.compose.ui.test.onNodeWithTag
 
 @DslMarker annotation class TransitionTestDsl
@@ -59,8 +63,21 @@ interface TransitionTestBuilder {
 
 @TransitionTestDsl
 interface TransitionTestAssertionScope {
-    /** Assert on [element]. */
-    fun onElement(element: ElementKey): SemanticsNodeInteraction
+    /**
+     * Assert on [element].
+     *
+     * Note that presence/value assertions on the returned [SemanticsNodeInteraction] will fail if 0
+     * or more than 1 elements matched [element]. If you need to assert on a shared element that
+     * will be present multiple times in the layout during transitions, either specify the [scene]
+     * in which you are matching or use [onSharedElement] instead.
+     */
+    fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction
+
+    /**
+     * Assert on a shared [element]. This will throw if [element] is not shared and present only in
+     * one scene during a transition.
+     */
+    fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection
 }
 
 /**
@@ -73,20 +90,22 @@ fun ComposeContentTestRule.testTransition(
     toSceneContent: @Composable SceneScope.() -> Unit,
     transition: TransitionBuilder.() -> Unit,
     layoutModifier: Modifier = Modifier,
+    fromScene: SceneKey = TestScenes.SceneA,
+    toScene: SceneKey = TestScenes.SceneB,
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        from = TestScenes.SceneA,
-        to = TestScenes.SceneB,
+        from = fromScene,
+        to = toScene,
         transitionLayout = { currentScene, onChangeScene ->
             SceneTransitionLayout(
                 currentScene,
                 onChangeScene,
-                transitions { from(TestScenes.SceneA, to = TestScenes.SceneB, transition) },
+                transitions { from(fromScene, to = toScene, transition) },
                 layoutModifier.fillMaxSize(),
             ) {
-                scene(TestScenes.SceneA, content = fromSceneContent)
-                scene(TestScenes.SceneB, content = toSceneContent)
+                scene(fromScene, content = fromSceneContent)
+                scene(toScene, content = toSceneContent)
             }
         },
         builder,
@@ -111,8 +130,24 @@ fun ComposeContentTestRule.testTransition(
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
-            override fun onElement(element: ElementKey): SemanticsNodeInteraction {
-                return this@testTransition.onNodeWithTag(element.name)
+            override fun onElement(
+                element: ElementKey,
+                scene: SceneKey?
+            ): SemanticsNodeInteraction {
+                return if (scene == null) {
+                    onNodeWithTag(element.testTag)
+                } else {
+                    onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag)))
+                }
+            }
+
+            override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection {
+                val interaction = onAllNodesWithTag(element.testTag)
+                val matches = interaction.fetchSemanticsNodes(atLeastOneRootRequired = false).size
+                if (matches < 2) {
+                    error("Element $element is not shared ($matches matches)")
+                }
+                return interaction
             }
         }
 
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fa94b25028a23e7b24e59930065fc7bfe93e654f
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.tween
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationRange
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TransitionDslTest {
+    @Test
+    fun emptyTransitions() {
+        val transitions = transitions {}
+        assertThat(transitions.transitionSpecs).isEmpty()
+    }
+
+    @Test
+    fun manyTransitions() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB)
+            from(TestScenes.SceneB, to = TestScenes.SceneC)
+            from(TestScenes.SceneC, to = TestScenes.SceneA)
+        }
+        assertThat(transitions.transitionSpecs).hasSize(3)
+    }
+
+    @Test
+    fun toFromBuilders() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB)
+            from(TestScenes.SceneB)
+            to(TestScenes.SceneC)
+        }
+
+        assertThat(transitions.transitionSpecs)
+            .comparingElementsUsing(
+                Correspondence.transforming<TransitionSpec, Pair<SceneKey?, SceneKey?>>(
+                    { it?.from to it?.to },
+                    "has (from, to) equal to"
+                )
+            )
+            .containsExactly(
+                TestScenes.SceneA to TestScenes.SceneB,
+                TestScenes.SceneB to null,
+                null to TestScenes.SceneC,
+            )
+    }
+
+    @Test
+    fun defaultTransitionSpec() {
+        val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) }
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.spec).isInstanceOf(SpringSpec::class.java)
+    }
+
+    @Test
+    fun customTransitionSpec() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween(durationMillis = 42) }
+        }
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.spec).isInstanceOf(TweenSpec::class.java)
+        assertThat((transition.spec as TweenSpec).durationMillis).isEqualTo(42)
+    }
+
+    @Test
+    fun defaultRange() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations.size).isEqualTo(1)
+        assertThat(transition.transformations.single().range).isEqualTo(null)
+    }
+
+    @Test
+    fun fractionRange() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+                fractionRange(start = 0.2f) { fade(TestElements.Foo) }
+                fractionRange(end = 0.9f) { fade(TestElements.Foo) }
+            }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 0.1f, end = 0.8f),
+                TransformationRange(start = 0.2f, end = TransformationRange.BoundUnspecified),
+                TransformationRange(start = TransformationRange.BoundUnspecified, end = 0.9f),
+            )
+    }
+
+    @Test
+    fun timestampRange() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                spec = tween(500)
+
+                timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+                timestampRange(startMillis = 200) { fade(TestElements.Foo) }
+                timestampRange(endMillis = 400) { fade(TestElements.Foo) }
+            }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 100 / 500f, end = 300 / 500f),
+                TransformationRange(start = 200 / 500f, end = TransformationRange.BoundUnspecified),
+                TransformationRange(start = TransformationRange.BoundUnspecified, end = 400 / 500f),
+            )
+    }
+
+    @Test
+    fun reversed() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                spec = tween(500)
+                reversed {
+                    fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+                    timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+                }
+            }
+        }
+
+        val transition = transitions.transitionSpecs.single()
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
+                TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f),
+            )
+    }
+
+    @Test
+    fun defaultReversed() {
+        val transitions = transitions {
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                spec = tween(500)
+                fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+                timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+            }
+        }
+
+        // Fetch the transition from B to A, which will automatically reverse the transition from A
+        // to B we defined.
+        val transition =
+            transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
+        assertThat(transition.transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
+                TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f),
+            )
+    }
+
+    companion object {
+        private val TRANSFORMATION_RANGE =
+            Correspondence.transforming<Transformation, TransformationRange?>(
+                { it?.range },
+                "has range equal to"
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2af3638602720ca5fc5e12d3ff4d3e728a847776
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TestScenes
+import com.android.compose.animation.scene.inScene
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.modifiers.size
+import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.onEach
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SharedElementTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testSharedElement() {
+        rule.testTransition(
+            fromSceneContent = {
+                // Foo is at (10, 50) with a size of (20, 80).
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo).size(20.dp, 80.dp))
+            },
+            toSceneContent = {
+                // Foo is at (50, 70) with a size of (10, 40).
+                Box(Modifier.offset(50.dp, 70.dp).element(TestElements.Foo).size(10.dp, 40.dp))
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+                // Elements should be shared by default.
+            }
+        ) {
+            before {
+                onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp)
+            }
+            at(0) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                    assertSizeIsEqualTo(20.dp, 80.dp)
+                }
+            }
+            at(16) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(20.dp, 55.dp)
+                    assertSizeIsEqualTo(17.5.dp, 70.dp)
+                }
+            }
+            at(32) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(30.dp, 60.dp)
+                    assertSizeIsEqualTo(15.dp, 60.dp)
+                }
+            }
+            at(48) {
+                onSharedElement(TestElements.Foo).onEach {
+                    assertPositionInRootIsEqualTo(40.dp, 65.dp)
+                    assertSizeIsEqualTo(12.5.dp, 50.dp)
+                }
+            }
+            after {
+                onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp)
+                onElement(TestElements.Foo).assertSizeIsEqualTo(10.dp, 40.dp)
+            }
+        }
+    }
+
+    @Test
+    fun testSharedElementDisabled() {
+        rule.testTransition(
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+            // The full layout is 100x100.
+            layoutModifier = Modifier.size(100.dp),
+            fromSceneContent = {
+                Box(Modifier.fillMaxSize()) {
+                    // Foo is at (10, 50).
+                    Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+                }
+            },
+            toSceneContent = {
+                Box(Modifier.fillMaxSize()) {
+                    // Foo is at (50, 60).
+                    Box(Modifier.offset(50.dp, 60.dp).element(TestElements.Foo))
+                }
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+
+                // Disable the shared element animation.
+                sharedElement(TestElements.Foo, enabled = false)
+
+                // In SceneA, Foo leaves to the left edge.
+                translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left)
+
+                // In SceneB, Foo comes from the bottom edge.
+                translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom)
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            at(0) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 100.dp)
+            }
+            at(16) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(7.5.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 90.dp)
+            }
+            at(32) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(5.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 80.dp)
+            }
+            at(48) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(2.5.dp, 50.dp)
+                onElement(TestElements.Foo, scene = TestScenes.SceneB)
+                    .assertPositionInRootIsEqualTo(50.dp, 70.dp)
+            }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 60.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d6f64bfe49741da80b7fdc82668b49e9268aebbe
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.compose.test
+
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionCollection
+
+/** Assert [assert] on each element of [this] [SemanticsNodeInteractionCollection]. */
+fun SemanticsNodeInteractionCollection.onEach(assert: SemanticsNodeInteraction.() -> Unit) {
+    for (i in 0 until this.fetchSemanticsNodes().size) {
+        get(i).assert()
+    }
+}
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 569dd4cf92528837645cebc4f37a3a837ddf963b..a51c55ee965f47b6e6a138f3a05a395c64c50239 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -52,7 +52,7 @@
             style="@style/Widget.Dialog.Button.BorderButton"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/cancel" />
+            android:text="@string/dismiss_dialog" />
 
         <Space
             android:layout_width="0dp"
@@ -64,6 +64,6 @@
             style="@style/Widget.Dialog.Button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@string/enable_display" />
+            android:text="@string/mirror_display" />
     </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 85fb3ac577bcc0700615528e8febb3df0eb26389..587caaf3ecf3e301c0c81c03709a6b642c756dba 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -44,6 +44,6 @@
 
     <!-- Whether to force split shade.
      For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
-     TODO (b/293290851) - change this comment/resource when flag is enabled -->
+     TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index e63229aea70b353589d9cf1385a5d5332b328582..fc6d20e11d3b34b1907fdc56a8a3f94a0896e4f7 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -38,6 +38,6 @@
 
     <!-- Whether to force split shade.
     For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
-    TODO (b/293290851) - change this comment/resource when flag is enabled -->
+    TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b6bca65b8174437b3a66d3bbc7a31b9e6466a1ef..3bdc0af7f7af20a386eafb96360b8fdc549ce0d9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -604,7 +604,7 @@
 
     <!-- Whether to force split shade.
     For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
-    TODO (b/293290851) - change this comment/resource when flag is enabled -->
+    TODO (b/293252410) - change this comment/resource when flag is enabled -->
     <bool name="force_config_use_split_notification_shade">false</bool>
 
     <!-- Whether we use large screen shade header which takes only one row compared to QS header -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5860806c6aec36a257d2a10a4cadaa7f671220f5..a2637d5e55c3864d377e2c08845302e5aae55bda 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3214,9 +3214,10 @@
 
     <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
     <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
-
     <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
-    <string name="enable_display">Enable display</string>
+    <string name="mirror_display">Mirror display</string>
+    <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+    <string name="dismiss_dialog">Dismiss</string>
 
     <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
     <string name="privacy_dialog_title">Microphone &amp; Camera</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ba8e427525869618b2a49961f968658212fba589..f6a0563ebf94f8072eda5fc624eef248dad62c74 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -184,6 +184,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
         }
     }
 
+    public boolean getSplitShadeCentered() {
+        return mSplitShadeCentered;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 29414ea7f4c0ae519788964b283d51fb2946ac5a..5646abefcef1be27fba73dbfb87b8b491258a7ce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -234,6 +234,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
         }
     }
 
+    public KeyguardClockSwitch getView() {
+        return mView;
+    }
+
     private void hideSliceViewAndNotificationIconContainer() {
         View ksv = mView.findViewById(R.id.keyguard_slice_view);
         ksv.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index d8486029a9030dfe3a1eb5c5532d9717a46f3a13..f9cc03eea288f5a5c239a64014794ebae472d501 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
 
 import static java.util.Collections.emptySet;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.os.Build;
@@ -78,6 +79,14 @@ public class KeyguardStatusView extends GridLayout {
         mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
 
         mMediaHostContainer = findViewById(R.id.status_view_media_container);
+        if (mMediaHostContainer != null) {
+            LayoutTransition mediaLayoutTransition = new LayoutTransition();
+            ((ViewGroup) mMediaHostContainer).setLayoutTransition(mediaLayoutTransition);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+            mediaLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
+        }
 
         updateDark();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8d0d299e56c2ab7bb8e0312c561cb6cf2a77b3c9..931ba6d97c5865a16eac1cc4b866b570f3d83433 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -23,6 +23,7 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CL
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.animation.Animator;
+import android.animation.LayoutTransition;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.res.Configuration;
@@ -101,6 +102,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
 
+    private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
 
     private DumpManager mDumpManager;
@@ -150,6 +152,48 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+        final View mediaHostContainer = mView.findViewById(R.id.status_view_media_container);
+        if (mediaHostContainer != null) {
+            mKeyguardClockSwitchController.getView().addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        if (!mSplitShadeEnabled
+                                || mKeyguardClockSwitchController.getView().getSplitShadeCentered()
+                                // Note: isKeyguardVisible() returns false after Launcher -> AOD.
+                                || !mKeyguardUpdateMonitor.isKeyguardVisible()) {
+                            return;
+                        }
+
+                        int oldHeight = oldBottom - oldTop;
+                        if (v.getHeight() == oldHeight) return;
+
+                        if (mediaHostContainer.getVisibility() != View.VISIBLE
+                                // If the media is appearing, also don't do the transition.
+                                || mediaHostContainer.getHeight() == 0) {
+                            return;
+                        }
+
+                        final LayoutTransition mediaLayoutTransition =
+                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
+                        if (mediaLayoutTransition == null) return;
+
+                        mediaLayoutTransition.enableTransitionType(LayoutTransition.CHANGING);
+                    });
+
+            mediaHostContainer.addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        final LayoutTransition mediaLayoutTransition =
+                                ((ViewGroup) mediaHostContainer).getLayoutTransition();
+                        if (mediaLayoutTransition == null) return;
+                        if (!mediaLayoutTransition.isTransitionTypeEnabled(
+                                LayoutTransition.CHANGING)) {
+                            return;
+                        }
+                        // Note: when this is called, the LayoutTransition is already been set up.
+                        // Disables the LayoutTransition until it's explicitly enabled again.
+                        mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGING);
+                    }
+            );
+        }
 
         mDumpManager.registerDumpable(getInstanceName(), this);
         if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
@@ -385,6 +429,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
      */
     public void setSplitShadeEnabled(boolean enabled) {
         mKeyguardClockSwitchController.setSplitShadeEnabled(enabled);
+        mSplitShadeEnabled = enabled;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 165c4bb018d373a7ea0d6939844f7d09994e8361..a81069a1f7dbd85cc3064e49573feb1aab08ebc2 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -424,11 +424,11 @@ public class LockIconViewController implements Dumpable {
     private void updateConfiguration() {
         WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
-        WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets();
         mWidthPixels = bounds.right;
         if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
             // Assumed to be initially neglected as there are no left or right insets in portrait
             // However, on landscape, these insets need to included when calculating the midpoint
+            WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets();
             mWidthPixels -= insets.getSystemWindowInsetLeft() + insets.getSystemWindowInsetRight();
         }
         mHeightPixels = bounds.bottom;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3b70555ad32a68757eed724f9070195d741abe6c..871257e6be79a69afa2074e85b9490efd3a088e2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -303,6 +303,11 @@ object Flags {
     @JvmField val MIGRATE_CLOCKS_TO_BLUEPRINT =
             unreleasedFlag("migrate_clocks_to_blueprint")
 
+    /** Migrate KeyguardRootView to use composables. */
+    // TODO(b/301969856): Tracking Bug.
+    @JvmField val KEYGUARD_ROOT_VIEW_USE_COMPOSE =
+        unreleasedFlag("keyguard_root_view_use_compose")
+
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
@@ -771,6 +776,10 @@ object Flags {
     // TODO(b/302087895): Tracking Bug
     @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
 
+    // TODO(b/302144438): Tracking Bug
+    @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
+            unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
+
     // 2900 - CentralSurfaces-related flags
 
     // TODO(b/285174336): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index fb02c7d68a9f7d880dcb34a2422880c3c51ab511..1761ca86f5883d813e65192d4da2778f2c5a816f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -26,7 +26,6 @@ import com.android.keyguard.LockIconView
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardStatusViewComponent
 import com.android.systemui.CoreStartable
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -40,7 +39,9 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -69,6 +70,7 @@ constructor(
     private val context: Context,
     private val keyguardIndicationController: KeyguardIndicationController,
     private val lockIconViewController: LockIconViewController,
+    private val shadeInteractor: ShadeInteractor,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -135,6 +137,7 @@ constructor(
                 occludingAppDeviceEntryMessageViewModel,
                 chipbarCoordinator,
                 keyguardStateController,
+                shadeInteractor
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index f564d0073e4290a3769d17baf4771caaa9ee9c45..053727ace76d0ad956f14e8d6e80441f1e690e89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -32,6 +32,7 @@ import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.ViewPriority
@@ -55,6 +56,7 @@ object KeyguardRootViewBinder {
         occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
         chipbarCoordinator: ChipbarCoordinator,
         keyguardStateController: KeyguardStateController,
+        shadeInteractor: ShadeInteractor,
     ): DisposableHandle {
         val disposableHandle =
             view.repeatWhenAttached {
@@ -88,6 +90,17 @@ object KeyguardRootViewBinder {
                             }
                         }
                     }
+
+                    launch {
+                        shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded ->
+                            view.visibility =
+                                if (isFullyAnyExpanded) {
+                                    View.INVISIBLE
+                                } else {
+                                    View.VISIBLE
+                                }
+                        }
+                    }
                 }
 
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 2ad74fbc6674d5e83fc96fecd7aee579e0447b76..864e345e9c1e71b46df6842f5d5ad07bfd3960b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -62,6 +62,7 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
@@ -109,6 +110,7 @@ constructor(
     private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
     private val chipbarCoordinator: ChipbarCoordinator,
     private val keyguardStateController: KeyguardStateController,
+    private val shadeInteractor: ShadeInteractor,
 ) {
 
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -317,6 +319,7 @@ constructor(
                 occludingAppDeviceEntryMessageViewModel,
                 chipbarCoordinator,
                 keyguardStateController,
+                shadeInteractor,
             )
         )
         rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
index 9409036586e808f50a9fd1fe05ff609df3e9e22f..f4bc7137b50cfb38166f8ec7cf12063ab92dea4d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
@@ -29,11 +29,11 @@ import androidx.constraintlayout.widget.ConstraintSet
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconView
 import com.android.keyguard.LockIconViewController
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import javax.inject.Inject
 
@@ -73,11 +73,11 @@ constructor(
         val mBottomPaddingPx =
             context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
         val bounds = windowManager.currentWindowMetrics.bounds
-        val insets = windowManager.currentWindowMetrics.windowInsets
         var widthPixels = bounds.right.toFloat()
         if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
             // Assumed to be initially neglected as there are no left or right insets in portrait.
             // However, on landscape, these insets need to included when calculating the midpoint.
+            val insets = windowManager.currentWindowMetrics.windowInsets
             widthPixels -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
         }
         val heightPixels = bounds.bottom.toFloat()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index ae3c912d6d1bc21388ce315a45384acee29e7223..ed6d41e5a75bc926f613dfe7ea8c810b9a53131e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -101,7 +101,8 @@ constructor(
     panelEventsEvents: ShadeStateEvents,
     private val secureSettings: SecureSettings,
     @Main private val handler: Handler,
-    private val splitShadeStateController: SplitShadeStateController
+    private val splitShadeStateController: SplitShadeStateController,
+    private val logger: MediaViewLogger,
 ) {
 
     /** Track the media player setting status on lock screen. */
@@ -1057,6 +1058,7 @@ constructor(
                     // that and directly set the mediaFrame's bounds within the premeasured host.
                     targetHost.addView(mediaFrame)
                 }
+                logger.logMediaHostAttachment(currentAttachmentLocation)
                 if (isCrossFadeAnimatorRunning) {
                     // When cross-fading with an animation, we only notify the media carousel of the
                     // location change, once the view is reattached to the new place and not
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index 8f1595d7d7a2fb49b7cffa4d24daae485b972ca0..3ff2315956ad694f9d99d687bbdcc2f70f4d507f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -52,4 +52,8 @@ class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogB
             { "location ($str1): $int1 -> $int2" }
         )
     }
+
+    fun logMediaHostAttachment(host: Int) {
+        buffer.log(TAG, LogLevel.DEBUG, { int1 = host }, { "Host (updateHostAttachment): $int1" })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
index fbf9294a0a377ba8b631001a0e21f3ddbe16f0c6..11d0be5fc8bf39d6219527df74047f478a2520a3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
@@ -14,20 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.mediaprojection
 
 import android.os.IBinder
 import android.os.Parcel
 import android.os.Parcelable
 
 /**
- * Class that represents an area that should be captured.
- * Currently it has only a launch cookie that represents a task but
- * we potentially could add more identifiers e.g. for a pair of tasks.
+ * Class that represents an area that should be captured. Currently it has only a launch cookie that
+ * represents a task but we potentially could add more identifiers e.g. for a pair of tasks.
  */
-data class MediaProjectionCaptureTarget(
-    val launchCookie: IBinder?
-): Parcelable {
+data class MediaProjectionCaptureTarget(val launchCookie: IBinder?) : Parcelable {
 
     constructor(parcel: Parcel) : this(parcel.readStrongBinder())
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index 9e616e2355e29ef54e20ebdf40981f156b648b6d..f1cade7512e29915bb273455ff990830b603e703 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media
+package com.android.systemui.mediaprojection
 
 import android.content.Context
 import android.media.projection.IMediaProjection
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index 88bc064c60f729f36783d189806316267f2a8900..b5d3e913cadbb5f98d6a303ddd9ac5d048388260 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.media
+package com.android.systemui.mediaprojection.appselector
 
 import android.app.ActivityOptions
 import android.content.Intent
@@ -46,13 +46,11 @@ import com.android.internal.app.chooser.TargetInfo
 import com.android.internal.widget.RecyclerView
 import com.android.internal.widget.RecyclerViewAccessibilityDelegate
 import com.android.internal.widget.ResolverDrawerLayout
-import com.android.systemui.res.R
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.MediaProjectionServiceHelper
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.AsyncActivityLauncher
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 33d9cc36c9b0b6c3a7bc75a6804ca97b3686de05..72aea040ba05956e332e84d309dc17dca560f5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -23,8 +23,6 @@ import android.os.UserHandle
 import androidx.lifecycle.DefaultLifecycleObserver
 import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.media.MediaProjectionPermissionActivity
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
 import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
@@ -37,6 +35,7 @@ import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRece
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
 import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
 import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
+import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
index 64006fe4c265ff0fafc320ec795f346c79d1b4d6..8b437c3225498dc58d283c9f6590f3bb87f06fd1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenrecord
+package com.android.systemui.mediaprojection.permission
 
 import android.content.Context
 import android.os.Bundle
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 4de6278400b32d2149b63fa1fcf0d3654921e20c..2b56d0cf9f83de5c81e56a83858232603a9b3a96 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media;
+package com.android.systemui.mediaprojection.permission;
 
 import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
 import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
@@ -22,8 +22,8 @@ import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
-import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
-import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
+import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
+import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -51,13 +51,13 @@ import android.text.style.StyleSpan;
 import android.util.Log;
 import android.view.Window;
 
-import com.android.systemui.res.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
-import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
-import com.android.systemui.screenrecord.ScreenShareOption;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.Utils;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
index 47e28d86e01d7dec6c0cdde6aedb4ca067e463f1..2f10ad3e64862d44e3fa1fcbbcc6b9d36f0a5f25 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenrecord
+package com.android.systemui.mediaprojection.permission
 
 import android.content.Context
 import android.media.projection.MediaProjectionConfig
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
index ebf0dd28fbf452292d548b72ef1bba293628572f..37e8d9f26ee34aff23148f56aa6688a4961b71ba 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.screenrecord
+package com.android.systemui.mediaprojection.permission
 
 import androidx.annotation.IntDef
 import androidx.annotation.StringRes
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9a9626d7c7a02d5aba2cac4dc6161e2733475470..10f95e0b7201e77d0474ab6a8ab13332fb97d5cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -48,6 +48,7 @@ import com.android.systemui.qs.nano.QsTileState;
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
+import com.android.systemui.qs.tiles.di.NewQSTileFactory;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
@@ -56,6 +57,8 @@ import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.settings.SecureSettings;
 
+import dagger.Lazy;
+
 import org.jetbrains.annotations.NotNull;
 
 import java.io.PrintWriter;
@@ -121,6 +124,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
 
     @Inject
     public QSTileHost(Context context,
+            Lazy<NewQSTileFactory> newQsTileFactoryProvider,
             QSFactory defaultFactory,
             @Main Executor mainExecutor,
             PluginManager pluginManager,
@@ -147,6 +151,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
 
         mShadeController = shadeController;
 
+        if (featureFlags.getPipelineTilesEnabled()) {
+            mQsFactories.add(newQsTileFactoryProvider.get());
+        }
         mQsFactories.add(defaultFactory);
         pluginManager.addPluginListener(this, QSFactory.class, true);
         mUserTracker = userTracker;
@@ -326,7 +333,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
                 try {
                     tile = createTile(tileSpec);
                     if (tile != null) {
-                        tile.setTileSpec(tileSpec);
                         if (tile.isAvailable()) {
                             newTiles.put(tileSpec, tile);
                             mQSLogger.logTileAdded(tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index a6226b36b0fd43d049dd2ecb6878243048c6210e..2af7ae0614ac424001ae7b52a57ea7e19e93ce7e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -130,11 +130,9 @@ public class TileQueryHelper {
             if (tile == null) {
                 continue;
             } else if (!tile.isAvailable()) {
-                tile.setTileSpec(spec);
                 tile.destroy();
                 continue;
             }
-            tile.setTileSpec(spec);
             tilesToAdd.add(tile);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 92490e8fbd43195c5338911c09e53079e3142fcf..a65967a0349b616c4ac2aa1b2b5b4f992084a571 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -31,6 +31,7 @@ import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.policy.CastController;
@@ -41,14 +42,14 @@ import com.android.systemui.statusbar.policy.SafetyController;
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import java.util.Map;
-
-import javax.inject.Named;
-
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.Multibinds;
 
+import java.util.Map;
+
+import javax.inject.Named;
+
 /**
  * Module for QS dependencies
  */
@@ -68,6 +69,11 @@ public interface QSModule {
     @Multibinds
     Map<String, QSTileImpl<?>> tileMap();
 
+    /** A map of internal QS tile ViewModels. Ensures that this can be injected even if
+     * it is empty */
+    @Multibinds
+    Map<String, QSTileViewModel> tileViewModelMap();
+
     @Provides
     @SysUISingleton
     static AutoTileManager provideAutoTileManager(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 00c23582f72664ceba722a7ba0db8119b7dabbd2..c5512c15ccc2205331008106a0662d131ae620c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -41,10 +41,12 @@ import com.android.systemui.qs.pipeline.domain.model.TileModel
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
 import com.android.systemui.qs.toProto
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
+import dagger.Lazy
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -131,6 +133,7 @@ constructor(
     private val installedTilesComponentRepository: InstalledTilesComponentRepository,
     private val userRepository: UserRepository,
     private val customTileStatePersister: CustomTileStatePersister,
+    private val newQSTileFactory: Lazy<NewQSTileFactory>,
     private val tileFactory: QSFactory,
     private val customTileAddedRepository: CustomTileAddedRepository,
     private val tileLifecycleManagerFactory: TileLifecycleManager.Factory,
@@ -139,7 +142,7 @@ constructor(
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
     private val logger: QSPipelineLogger,
-    featureFlags: QSPipelineFlagsRepository,
+    private val featureFlags: QSPipelineFlagsRepository,
 ) : CurrentTilesInteractor {
 
     private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> =
@@ -333,12 +336,19 @@ constructor(
     }
 
     private suspend fun createTile(spec: TileSpec): QSTile? {
-        val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) }
+        val tile =
+            withContext(mainDispatcher) {
+                if (featureFlags.pipelineTilesEnabled) {
+                    newQSTileFactory.get().createTile(spec.spec)
+                } else {
+                    null
+                }
+                    ?: tileFactory.createTile(spec.spec)
+            }
         if (tile == null) {
             logger.logTileNotFoundInFactory(spec)
             return null
         } else {
-            tile.tileSpec = spec.spec
             return if (!tile.isAvailable) {
                 logger.logTileDestroyed(
                     spec,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 551b0f4890a4f4400de6ba19c0860b55d5ed2531..1a71b715fe3a7f2cb79869da3a01d5dcbb22f35c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -1,7 +1,7 @@
 package com.android.systemui.qs.pipeline.shared
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
@@ -10,7 +10,7 @@ import javax.inject.Inject
 class QSPipelineFlagsRepository
 @Inject
 constructor(
-    private val featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlagsClassic,
 ) {
 
     /** @see Flags.QS_PIPELINE_NEW_HOST */
@@ -20,4 +20,8 @@ constructor(
     /** @see Flags.QS_PIPELINE_AUTO_ADD */
     val pipelineAutoAddEnabled: Boolean
         get() = pipelineHostEnabled && featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+
+    /** @see Flags.QS_PIPELINE_NEW_TILES */
+    val pipelineTilesEnabled: Boolean
+        get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_TILES)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index 11b5dd7cb03676c092fce114a496107daa9e7c8d..aed08f8b5457b61e8699fa788838bb4179e774e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -31,11 +31,7 @@ import com.android.systemui.qs.external.CustomTile
 sealed class TileSpec private constructor(open val spec: String) {
 
     /** Represents a spec that couldn't be parsed into a valid type of tile. */
-    object Invalid : TileSpec("") {
-        override fun toString(): String {
-            return "TileSpec.INVALID"
-        }
-    }
+    data object Invalid : TileSpec("")
 
     /** Container for the spec of a tile provided by SystemUI. */
     data class PlatformTileSpec
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 9c7a734125184f4dbd4f6dffd6cc51132b4ea78e..632aa6330a18fa9002fd4ed3857eb74c6e618d15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -70,6 +70,7 @@ public class QSFactoryImpl implements QSFactory {
         if (tile != null) {
             tile.initialize();
             tile.postStale(); // Tile was just created, must be stale.
+            tile.setTileSpec(tileSpec);
         }
         return tile;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
index e9f907c4d8e77baa756788c8d7bd4e8ff61492e2..dc9e11567676415aab86690f6f2ccd81b83ad64e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
@@ -1,11 +1,11 @@
 package com.android.systemui.qs.tiles.base.actions
 
 import android.content.Intent
+import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import javax.inject.Inject
 
 /**
@@ -17,9 +17,9 @@ class QSTileIntentUserActionHandler
 @Inject
 constructor(private val activityStarter: ActivityStarter) {
 
-    fun handle(userAction: QSTileUserAction, intent: Intent) {
+    fun handle(view: View?, intent: Intent) {
         val animationController: ActivityLaunchAnimator.Controller? =
-            userAction.view?.let {
+            view?.let {
                 ActivityLaunchAnimator.Controller.fromView(
                     it,
                     InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index c2a75fac60f54548d8edbace31c482320eecf0a4..bb4de808de79a3ecff1d1b224209f3b8aedab1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -92,7 +92,7 @@ constructor(
             .stateIn(
                 tileScope,
                 SharingStarted.WhileSubscribed(),
-                false,
+                true,
             )
 
     private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3fedbfc6671d71955e22b13eba69bb21fc3a5a5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -0,0 +1,28 @@
+package com.android.systemui.qs.tiles.di
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
+import javax.inject.Inject
+import javax.inject.Provider
+
+// TODO(b/http://b/299909989): Rename the factory after rollout
+@SysUISingleton
+class NewQSTileFactory
+@Inject
+constructor(
+    private val adapterFactory: QSTileViewModelAdapter.Factory,
+    private val tileMap:
+        Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
+) : QSFactory {
+
+    override fun createTile(tileSpec: String): QSTile? =
+        tileMap[tileSpec]?.let {
+            val tile = it.get()
+            tile.onLifecycle(QSTileLifecycle.ALIVE)
+            adapterFactory.create(tile)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index a5eaac154230750e2ed5ba8a83d13dc7887f70b3..019d3c0ee4162c68292c9ba8ebece065b21d1516 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -1,14 +1,13 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.graphics.drawable.Icon
+import androidx.annotation.StringRes
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 data class QSTileConfig(
     val tileSpec: TileSpec,
     val tileIcon: Icon,
-    val tileLabel: CharSequence,
-// TODO(b/299908705): Fill necessary params
-/*
-val instanceId: InstanceId,
- */
+    @StringRes val tileLabelRes: Int,
+    val instanceId: InstanceId,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 53f9edfb954c8074833edb974a1c067284c8798e..dc5c69080fd02304225903ab73ac9949beb92bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -1,18 +1,96 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.graphics.drawable.Icon
+import android.service.quicksettings.Tile
+import com.android.systemui.common.shared.model.Icon
 
+/**
+ * Represents current a state of the tile to be displayed in on the view. Consider using
+ * [QSTileState.build] for better state creation experience and preset default values for certain
+ * fields.
+ *
+ * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
+ */
 data class QSTileState(
-    val icon: Icon,
+    val icon: () -> Icon,
     val label: CharSequence,
-// TODO(b/299908705): Fill necessary params
-/*
-   val subtitle: CharSequence = "",
-   val activeState: ActivationState = Active,
-   val enabledState: Enabled = Enabled,
-   val loopIconAnimation: Boolean = false,
-   val secondaryIcon: Icon? = null,
-   val slashState: SlashState? = null,
-   val supportedActions: Collection<UserAction> = listOf(Click), clicks should be a default action
-*/
-)
+    val activationState: ActivationState,
+    val secondaryLabel: CharSequence?,
+    val supportedActions: Set<UserAction>,
+    val contentDescription: CharSequence?,
+    val stateDescription: CharSequence?,
+    val sideViewIcon: SideViewIcon,
+    val enabledState: EnabledState,
+    val expandedAccessibilityClassName: String?,
+) {
+
+    companion object {
+
+        fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+            Builder(icon, label).apply(build).build()
+
+        fun build(icon: Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+            build({ icon }, label, build)
+    }
+
+    enum class ActivationState(val legacyState: Int) {
+        // An unavailable state indicates that for some reason this tile is not currently available
+        // to the user, and will have no click action. The tile's icon will be tinted differently to
+        // reflect this state.
+        UNAVAILABLE(Tile.STATE_UNAVAILABLE),
+        // This represents a tile that is currently active. (e.g. wifi is connected, bluetooth is
+        // on, cast is casting). This is the default state.
+        ACTIVE(Tile.STATE_ACTIVE),
+        // This represents a tile that is currently in a disabled state but is still interactable. A
+        // disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
+        // bluetooth disabled), but is still interactable by the user to modify this state.
+        INACTIVE(Tile.STATE_INACTIVE),
+    }
+
+    /**
+     * Enabled tile behaves as usual where is disabled one is frozen and inactive in its current
+     * [ActivationState].
+     */
+    enum class EnabledState {
+        ENABLED,
+        DISABLED,
+    }
+
+    enum class UserAction {
+        CLICK,
+        LONG_CLICK,
+    }
+
+    sealed interface SideViewIcon {
+        data class Custom(val icon: Icon) : SideViewIcon
+        data object Chevron : SideViewIcon
+        data object None : SideViewIcon
+    }
+
+    class Builder(
+        var icon: () -> Icon,
+        var label: CharSequence,
+    ) {
+        var activationState: ActivationState = ActivationState.INACTIVE
+        var secondaryLabel: CharSequence? = null
+        var supportedActions: Set<UserAction> = setOf(UserAction.CLICK)
+        var contentDescription: CharSequence? = null
+        var stateDescription: CharSequence? = null
+        var sideViewIcon: SideViewIcon = SideViewIcon.None
+        var enabledState: EnabledState = EnabledState.ENABLED
+        var expandedAccessibilityClassName: String? = null
+
+        fun build(): QSTileState =
+            QSTileState(
+                icon,
+                label,
+                activationState,
+                secondaryLabel,
+                supportedActions,
+                contentDescription,
+                stateDescription,
+                sideViewIcon,
+                enabledState,
+                expandedAccessibilityClassName,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
index f1f8f0152c677a4ba943bdebdaac2437f43f7186..0b232c28d3f4d766e3f4399dc7d283e0173f707b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt
@@ -1,13 +1,11 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.content.Context
 import android.view.View
 
 sealed interface QSTileUserAction {
 
-    val context: Context
     val view: View?
 
-    class Click(override val context: Context, override val view: View?) : QSTileUserAction
-    class LongClick(override val context: Context, override val view: View?) : QSTileUserAction
+    class Click(override val view: View?) : QSTileUserAction
+    class LongClick(override val view: View?) : QSTileUserAction
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d4bdb77c2b41d998cf38407b9c0320b69761bd08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -0,0 +1,232 @@
+package com.android.systemui.qs.tiles.viewmodel
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import androidx.annotation.GuardedBy
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.function.Supplier
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.collectIndexed
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
+class QSTileViewModelAdapter
+@AssistedInject
+constructor(
+    private val qsHost: QSHost,
+    @Assisted private val qsTileViewModel: QSTileViewModel,
+) : QSTile {
+
+    private val context
+        get() = qsHost.context
+
+    @GuardedBy("callbacks")
+    private val callbacks: MutableCollection<QSTile.Callback> = mutableSetOf()
+    @GuardedBy("listeningClients")
+    private val listeningClients: MutableCollection<Any> = mutableSetOf()
+
+    // Cancels the jobs when the adapter is no longer alive
+    private val adapterScope = CoroutineScope(SupervisorJob())
+    // Cancels the jobs when clients stop listening
+    private val listeningScope = CoroutineScope(SupervisorJob())
+
+    init {
+        adapterScope.launch {
+            qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+                if (!isAvailable) {
+                    qsHost.removeTile(tileSpec)
+                }
+                // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's
+                // why we only allow isAvailable == true once and throw an exception afterwards.
+                if (index > 0 && isAvailable) {
+                    // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional
+                    // guidance on how to auto add your tile
+                    throw UnsupportedOperationException("Turning on tile is not supported now")
+                }
+            }
+        }
+
+        // QSTileHost doesn't call this when userId is initialized
+        userSwitch(qsHost.userId)
+
+        if (DEBUG) {
+            Log.d(TAG, "Using new tiles for: $tileSpec")
+        }
+    }
+
+    override fun isAvailable(): Boolean = qsTileViewModel.isAvailable.value
+
+    override fun setTileSpec(tileSpec: String?) {
+        throw UnsupportedOperationException("Tile spec is immutable in new tiles")
+    }
+
+    override fun refreshState() {
+        qsTileViewModel.forceUpdate()
+    }
+
+    override fun addCallback(callback: QSTile.Callback?) {
+        callback ?: return
+        synchronized(callbacks) { callbacks.add(callback) }
+    }
+
+    override fun removeCallback(callback: QSTile.Callback?) {
+        callback ?: return
+        synchronized(callbacks) { callbacks.remove(callback) }
+    }
+
+    override fun removeCallbacks() {
+        synchronized(callbacks) { callbacks.clear() }
+    }
+
+    override fun click(view: View?) {
+        if (isActionSupported(QSTileState.UserAction.CLICK)) {
+            qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+        }
+    }
+
+    override fun secondaryClick(view: View?) {
+        if (isActionSupported(QSTileState.UserAction.CLICK)) {
+            qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view))
+        }
+    }
+
+    override fun longClick(view: View?) {
+        if (isActionSupported(QSTileState.UserAction.LONG_CLICK)) {
+            qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(view))
+        }
+    }
+
+    private fun isActionSupported(action: QSTileState.UserAction): Boolean =
+        qsTileViewModel.currentState?.supportedActions?.contains(action) == true
+
+    override fun userSwitch(currentUser: Int) {
+        qsTileViewModel.onUserIdChanged(currentUser)
+    }
+
+    @Deprecated(
+        "Not needed as {@link com.android.internal.logging.UiEvent} will use #getMetricsSpec",
+        replaceWith = ReplaceWith("getMetricsSpec"),
+    )
+    override fun getMetricsCategory(): Int = 0
+
+    override fun setListening(client: Any?, listening: Boolean) {
+        client ?: return
+        synchronized(listeningClients) {
+            if (listening) {
+                listeningClients.add(client)
+                if (listeningClients.size == 1) {
+                    qsTileViewModel.state
+                        .map { mapState(context, it, qsTileViewModel.config) }
+                        .onEach { legacyState ->
+                            synchronized(callbacks) {
+                                callbacks.forEach { it.onStateChanged(legacyState) }
+                            }
+                        }
+                        .launchIn(listeningScope)
+                }
+            } else {
+                listeningClients.remove(client)
+                if (listeningClients.isEmpty()) {
+                    listeningScope.coroutineContext.cancelChildren()
+                }
+            }
+        }
+    }
+
+    override fun isListening(): Boolean =
+        synchronized(listeningClients) { listeningClients.isNotEmpty() }
+
+    override fun setDetailListening(show: Boolean) {
+        // do nothing like QSTileImpl
+    }
+
+    override fun destroy() {
+        adapterScope.cancel()
+        listeningScope.cancel()
+        qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD)
+    }
+
+    override fun getState(): QSTile.State? =
+        qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
+
+    override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
+    override fun getTileLabel(): CharSequence =
+        context.getString(qsTileViewModel.config.tileLabelRes)
+    override fun getTileSpec(): String = qsTileViewModel.config.tileSpec.spec
+
+    private companion object {
+
+        const val DEBUG = false
+        const val TAG = "QSTileVMAdapter"
+
+        fun mapState(
+            context: Context,
+            viewModelState: QSTileState,
+            config: QSTileConfig
+        ): QSTile.State =
+            // we have to use QSTile.BooleanState to support different side icons
+            // which are bound to instanceof QSTile.BooleanState in QSTileView.
+            QSTile.BooleanState().apply {
+                spec = config.tileSpec.spec
+                label = viewModelState.label
+                // This value is synthetic and doesn't have any meaning
+                value = false
+
+                secondaryLabel = viewModelState.secondaryLabel
+                handlesLongClick =
+                    viewModelState.supportedActions.contains(QSTileState.UserAction.LONG_CLICK)
+
+                iconSupplier = Supplier {
+                    when (val stateIcon = viewModelState.icon()) {
+                        is Icon.Loaded -> DrawableIcon(stateIcon.drawable)
+                        is Icon.Resource -> ResourceIcon.get(stateIcon.res)
+                    }
+                }
+                state = viewModelState.activationState.legacyState
+
+                contentDescription = viewModelState.contentDescription
+                stateDescription = viewModelState.stateDescription
+
+                disabledByPolicy = viewModelState.enabledState == QSTileState.EnabledState.DISABLED
+                expandedAccessibilityClassName = viewModelState.expandedAccessibilityClassName
+
+                when (viewModelState.sideViewIcon) {
+                    is QSTileState.SideViewIcon.Custom -> {
+                        sideViewCustomDrawable =
+                            when (viewModelState.sideViewIcon.icon) {
+                                is Icon.Loaded -> viewModelState.sideViewIcon.icon.drawable
+                                is Icon.Resource ->
+                                    context.getDrawable(viewModelState.sideViewIcon.icon.res)
+                            }
+                    }
+                    is QSTileState.SideViewIcon.Chevron -> {
+                        forceExpandIcon = true
+                    }
+                    is QSTileState.SideViewIcon.None -> {
+                        forceExpandIcon = false
+                    }
+                }
+            }
+    }
+
+    @AssistedFactory
+    interface Factory {
+
+        fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 7cdb6553c47ebf5725484ba4ec9c480f613610e6..3501b6bc045e29ec40d241380677bdc81905108e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -41,10 +41,10 @@ import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.media.MediaProjectionCaptureTarget;
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
+import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index b80a01212ca0a96cb97544bb52af79e049ee5a86..3aab3bf628095d9e6cc9be540d7d9227517a79d5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -51,7 +51,7 @@ import android.util.Size;
 import android.view.Surface;
 import android.view.WindowManager;
 
-import com.android.systemui.media.MediaProjectionCaptureTarget;
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
 
 import java.io.Closeable;
 import java.io.File;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index f0ce8a465d0d872aff76d9d444bc6008f30cb743..f2e94e94757fb239f977ed94d7d44ee26cd792be 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -18,7 +18,7 @@ package com.android.systemui.screenrecord;
 
 import static android.app.Activity.RESULT_OK;
 
-import static com.android.systemui.media.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
+import static com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
@@ -41,8 +41,8 @@ import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
 import com.android.systemui.res.R;
-import com.android.systemui.media.MediaProjectionCaptureTarget;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index b5b7043e37a08415a1b501660fafe4e8371abd11..a1d5d98ba9e92b78b893537b82deb20d3d80faa9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -32,10 +32,14 @@ import android.widget.ArrayAdapter
 import android.widget.Spinner
 import android.widget.Switch
 import androidx.annotation.LayoutRes
-import com.android.systemui.res.R
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.media.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
+import com.android.systemui.mediaprojection.permission.BaseScreenSharePermissionDialog
+import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
+import com.android.systemui.mediaprojection.permission.SINGLE_APP
+import com.android.systemui.mediaprojection.permission.ScreenShareOption
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
 
 /** Dialog to select screen recording options */
@@ -58,6 +62,7 @@ class ScreenRecordPermissionDialog(
     private lateinit var tapsView: View
     private lateinit var audioSwitch: Switch
     private lateinit var options: Spinner
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setDialogTitle(R.string.screenrecord_permission_dialog_title)
@@ -177,6 +182,7 @@ class ScreenRecordPermissionDialog(
             )
         private const val DELAY_MS: Long = 3000
         private const val INTERVAL_MS: Long = 1000
+
         private fun createOptionList(): List<ScreenShareOption> {
             return listOf(
                 ScreenShareOption(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index b7152370624eab790537415472dedd0237725ddb..6fa592c6dd7817d7b923b6bb5e08bd78baca177b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,12 +30,13 @@ import androidx.annotation.Nullable;
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.ViewController;
@@ -283,6 +284,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
         private final VibratorHelper mVibratorHelper;
         private final SystemClock mSystemClock;
         private final CoroutineDispatcher mMainDispatcher;
+        private final ActivityStarter mActivityStarter;
 
         @Inject
         public Factory(
@@ -291,14 +293,15 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
                 VibratorHelper vibratorHelper,
                 SystemClock clock,
                 FeatureFlagsClassic featureFlags,
-                @Main CoroutineDispatcher mainDispatcher
-        ) {
+                @Main CoroutineDispatcher mainDispatcher,
+                ActivityStarter activityStarter) {
             mFalsingManager = falsingManager;
             mUiEventLogger = uiEventLogger;
             mFeatureFlags = featureFlags;
             mVibratorHelper = vibratorHelper;
             mSystemClock = clock;
             mMainDispatcher = mainDispatcher;
+            mActivityStarter = activityStarter;
         }
 
         /**
@@ -314,6 +317,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
             int layout = getLayout();
             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                     .inflate(layout, viewRoot, false);
+            root.setActivityStarter(mActivityStarter);
+
             BrightnessSliderHapticPlugin plugin;
             if (mFeatureFlags.isEnabled(HAPTIC_BRIGHTNESS_SLIDER)) {
                 plugin = new BrightnessSliderHapticPluginImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c8854922418393a1f4fd0ee208e8e41600b7bbf6..5ecf07f5a264c6150509bf232c6fec4ecb2c6568 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -33,6 +33,7 @@ import androidx.annotation.Nullable;
 
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.res.R;
 
 /**
@@ -41,6 +42,7 @@ import com.android.systemui.res.R;
  */
 public class BrightnessSliderView extends FrameLayout {
 
+    private ActivityStarter mActivityStarter;
     @NonNull
     private ToggleSeekBar mSlider;
     private DispatchTouchEventListener mListener;
@@ -57,6 +59,10 @@ public class BrightnessSliderView extends FrameLayout {
         super(context, attrs);
     }
 
+    public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
+
     // Inflated from quick_settings_brightness_dialog
     @Override
     protected void onFinishInflate() {
@@ -65,6 +71,7 @@ public class BrightnessSliderView extends FrameLayout {
 
         mSlider = requireViewById(R.id.slider);
         mSlider.setAccessibilityLabel(getContentDescription().toString());
+        mSlider.setActivityStarter(mActivityStarter);
 
         // Finds the progress drawable. Assumes brightness_progress_drawable.xml
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index a5a0ae70045eade966d51645567640ed62e360cd..6ec10da28000bac91ded6a1e333b806bd9b589b8 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -23,8 +23,9 @@ import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.SeekBar;
 
+import androidx.annotation.NonNull;
+
 import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ActivityStarter;
 
 public class ToggleSeekBar extends SeekBar {
@@ -32,6 +33,8 @@ public class ToggleSeekBar extends SeekBar {
 
     private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
 
+    private ActivityStarter mActivityStarter;
+
     public ToggleSeekBar(Context context) {
         super(context);
     }
@@ -49,7 +52,7 @@ public class ToggleSeekBar extends SeekBar {
         if (mEnforcedAdmin != null) {
             Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
                     mContext, mEnforcedAdmin);
-            Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
             return true;
         }
         if (!isEnabled()) {
@@ -74,4 +77,8 @@ public class ToggleSeekBar extends SeekBar {
     public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
         mEnforcedAdmin = admin;
     }
+
+    public void setActivityStarter(@NonNull ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 251cc168161c317959a5b7a33f12446bdb7a663e..ac8333ae84adc5d2c23a883c4b6e68a8f06e71e9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -129,6 +129,9 @@ constructor(
         combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
+    /** Whether either the shade or QS is fully expanded. */
+    val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
+
     /** Whether either the shade or QS is expanding from a fully collapsed state. */
     val isAnyExpanding: Flow<Boolean> =
         anyExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 8baab25e5c590ec152045af72eac21d6a9ee010b..f616b91c47120ee4055b4ddc6aae48cde7106620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -244,12 +244,16 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
             }
             final float scaledImageWidth = drawableWidth * scaleToFitIconView;
             final float scaledImageHeight = drawableHeight * scaleToFitIconView;
-            // if the scaled image size <= mOriginalStatusBarIconSize, we don't need to enlarge it
             scaleToOriginalDrawingSize = Math.min(
                     (float) mOriginalStatusBarIconSize / scaledImageWidth,
                     (float) mOriginalStatusBarIconSize / scaledImageHeight);
             if (scaleToOriginalDrawingSize > 1.0f) {
-                scaleToOriginalDrawingSize = 1.0f;
+                // per b/296026932, if the scaled image size <= mOriginalStatusBarIconSize, we need
+                // to scale up the scaled image to fit in mOriginalStatusBarIconSize. But if both
+                // the raw drawable intrinsic width/height are less than mOriginalStatusBarIconSize,
+                // then we just scale up the scaled image back to the raw drawable size.
+                scaleToOriginalDrawingSize = Math.min(
+                        scaleToOriginalDrawingSize, 1f / scaleToFitIconView);
             }
         }
         iconScale = scaleToOriginalDrawingSize;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index efa092bb3f40661aeffe58eba8f837db863a32e8..250fe53a2df6fb5de32eb36ab775c62837d151bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -22,11 +22,12 @@ import androidx.annotation.StringRes
 import androidx.annotation.VisibleForTesting
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 
@@ -90,50 +91,56 @@ sealed interface WifiIcon : Diffable<WifiIcon> {
                             )}"
                         )
                     )
-                is WifiNetworkModel.Active -> {
-                    val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
-                    val contentDescription =
-                        ContentDescription.Loaded(
-                            if (model.isValidated) {
-                                (levelDesc)
-                            } else {
-                                "$levelDesc,${context.getString(NO_INTERNET)}"
-                            }
-                        )
-                    Visible(model.toIcon(showHotspotInfo), contentDescription)
-                }
+                is WifiNetworkModel.Active -> model.toIcon(showHotspotInfo, context)
             }
 
-        @DrawableRes
-        private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int {
-            return if (!showHotspotInfo) {
-                this.toBasicIcon()
+        private fun WifiNetworkModel.Active.toIcon(
+            showHotspotInfo: Boolean,
+            context: Context,
+        ): Visible {
+            return if (
+                !showHotspotInfo ||
+                    this.hotspotDeviceType == WifiNetworkModel.HotspotDeviceType.NONE
+            ) {
+                this.toBasicIcon(context)
             } else {
-                when (this.hotspotDeviceType) {
-                    WifiNetworkModel.HotspotDeviceType.NONE -> this.toBasicIcon()
-                    WifiNetworkModel.HotspotDeviceType.TABLET ->
-                        com.android.settingslib.R.drawable.ic_hotspot_tablet
-                    WifiNetworkModel.HotspotDeviceType.LAPTOP ->
-                        com.android.settingslib.R.drawable.ic_hotspot_laptop
-                    WifiNetworkModel.HotspotDeviceType.WATCH ->
-                        com.android.settingslib.R.drawable.ic_hotspot_watch
-                    WifiNetworkModel.HotspotDeviceType.AUTO ->
-                        com.android.settingslib.R.drawable.ic_hotspot_auto
-                    // Use phone as the default drawable
-                    WifiNetworkModel.HotspotDeviceType.PHONE,
-                    WifiNetworkModel.HotspotDeviceType.UNKNOWN,
-                    WifiNetworkModel.HotspotDeviceType.INVALID ->
-                        com.android.settingslib.R.drawable.ic_hotspot_phone
-                }
+                val icon =
+                    when (this.hotspotDeviceType) {
+                        WifiNetworkModel.HotspotDeviceType.TABLET ->
+                            com.android.settingslib.R.drawable.ic_hotspot_tablet
+                        WifiNetworkModel.HotspotDeviceType.LAPTOP ->
+                            com.android.settingslib.R.drawable.ic_hotspot_laptop
+                        WifiNetworkModel.HotspotDeviceType.WATCH ->
+                            com.android.settingslib.R.drawable.ic_hotspot_watch
+                        WifiNetworkModel.HotspotDeviceType.AUTO ->
+                            com.android.settingslib.R.drawable.ic_hotspot_auto
+                        // Use phone as the default drawable
+                        WifiNetworkModel.HotspotDeviceType.PHONE,
+                        WifiNetworkModel.HotspotDeviceType.UNKNOWN,
+                        WifiNetworkModel.HotspotDeviceType.INVALID ->
+                            com.android.settingslib.R.drawable.ic_hotspot_phone
+                        WifiNetworkModel.HotspotDeviceType.NONE ->
+                            throw IllegalStateException("NONE checked earlier")
+                    }
+                Visible(
+                    icon,
+                    ContentDescription.Loaded(context.getString(WIFI_OTHER_DEVICE_CONNECTION)),
+                )
             }
         }
 
-        @DrawableRes
-        private fun WifiNetworkModel.Active.toBasicIcon(): Int {
+        private fun WifiNetworkModel.Active.toBasicIcon(context: Context): Visible {
+            val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
             return if (this.isValidated) {
-                WifiIcons.WIFI_FULL_ICONS[this.level]
+                Visible(
+                    WifiIcons.WIFI_FULL_ICONS[this.level],
+                    ContentDescription.Loaded(levelDesc),
+                )
             } else {
-                WifiIcons.WIFI_NO_INTERNET_ICONS[this.level]
+                Visible(
+                    WifiIcons.WIFI_NO_INTERNET_ICONS[this.level],
+                    ContentDescription.Loaded("$levelDesc,${context.getString(NO_INTERNET)}"),
+                )
             }
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index ba3dbf0a0cb7b8c1151540fdc804de0dda3aaa31..484b1194eb8b1a406fae7b193d2ab720dd56b813 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -20,8 +20,10 @@ import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.LayoutTransition;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.logging.KeyguardLogger;
@@ -44,6 +46,7 @@ import org.mockito.MockitoAnnotations;
 public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
 
     @Mock protected KeyguardStatusView mKeyguardStatusView;
+
     @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
     @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
     @Mock protected KeyguardStateController mKeyguardStateController;
@@ -61,6 +64,10 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
 
     protected KeyguardStatusViewController mController;
 
+    @Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
+    @Mock protected FrameLayout mMediaHostContainer;
+    @Mock protected LayoutTransition mMediaLayoutTransition;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -93,6 +100,8 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
                 };
 
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+
+        when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
     }
 
     protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index b8b0198f94dfc8f6cd426e9320f1d464d9a6a495..e4e2b0a8d89f1b79efc291dbfef3d96f48cd1ed0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -18,14 +18,18 @@ package com.android.keyguard;
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.LayoutTransition;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.View;
 
+import com.android.systemui.res.R;
 import com.android.systemui.plugins.ClockConfig;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -124,4 +128,112 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll
         mController.onDestroy();
         verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName()));
     }
+
+    @Test
+    public void onInit_addsOnLayoutChangeListenerToClockSwitch() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
+    }
+
+    @Test
+    public void onInit_addsOnLayoutChangeListenerToMediaHostContainer() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
+    }
+
+    @Test
+    public void clockSwitchHeightChanged_mediaChangingLayoutTransitionEnabled() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
+
+        // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
+        // Below here is the actual test.
+
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        mController.setSplitShadeEnabled(true);
+        when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
+        when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
+        when(mMediaHostContainer.getHeight()).thenReturn(200);
+        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
+
+        when(mKeyguardClockSwitch.getHeight()).thenReturn(0);
+        listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
+                0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
+                0, /* oldBottom = */ 200);
+        verify(mMediaLayoutTransition).enableTransitionType(LayoutTransition.CHANGING);
+    }
+
+    @Test
+    public void clockSwitchHeightNotChanged_mediaChangingLayoutTransitionNotEnabled() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
+
+        // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
+        // Below here is the actual test.
+
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        mController.setSplitShadeEnabled(true);
+        when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
+        when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
+        when(mMediaHostContainer.getHeight()).thenReturn(200);
+        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
+
+        when(mKeyguardClockSwitch.getHeight()).thenReturn(200);
+        listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
+                0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
+                0, /* oldBottom = */ 200);
+        verify(mMediaLayoutTransition, never()).enableTransitionType(LayoutTransition.CHANGING);
+    }
+
+    @Test
+    public void onMediaHostContainerLayout_disablesChangingLayoutTransition() {
+        when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
+                mMediaHostContainer);
+
+        mController.onInit();
+
+        ArgumentCaptor<View.OnLayoutChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+        verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
+
+        // Above here is the same as `onInit_addsOnLayoutChangeListenerToMediaHostContainer`.
+        // Below here is the actual test.
+
+        View.OnLayoutChangeListener listener = captor.getValue();
+
+        when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
+
+        when(mMediaLayoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).thenReturn(
+                true);
+        listener.onLayoutChange(mMediaHostContainer, 1, 2, 3, 4, 1, 2, 3, 4);
+        verify(mMediaLayoutTransition).disableTransitionType(LayoutTransition.CHANGING);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 86439e557f8b4da0a1c48375599461aaf5b95a52..58d372c68c55440c6092029bd8936575ba768a5b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,5 +1,6 @@
 package com.android.keyguard
 
+import android.animation.LayoutTransition
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -34,6 +35,20 @@ class KeyguardStatusViewTest : SysuiTestCase() {
                 as KeyguardStatusView
     }
 
+    @Test
+    fun mediaViewHasLayoutTransitionInDisabledState() {
+        val layoutTransition = (mediaView as ViewGroup).layoutTransition
+        assertThat(layoutTransition).isNotNull()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_APPEARING))
+            .isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_DISAPPEARING))
+            .isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.APPEARING)).isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.DISAPPEARING))
+            .isFalse()
+        assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).isFalse()
+    }
+
     @Test
     fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
         val translationY = 1234f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 25faeef85e500fa3ae284838561b57e47a4bd03a..de57b603c7fe835d248a9ea2b04975470113c9df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -81,6 +81,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
     @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
+    @Mock lateinit var logger: MediaViewLogger
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -121,7 +122,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
                 notifPanelEvents,
                 settings,
                 fakeHandler,
-                ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                logger,
             )
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index b595e8de3bad5f4189b88057193c9f26e5efa645..79411f427f1fbae91e3082fddd76d48407961ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -66,6 +66,7 @@ import com.android.systemui.qs.external.TileServiceKey;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.di.NewQSTileFactory;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
@@ -77,6 +78,8 @@ import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -101,8 +104,6 @@ public class QSTileHostTest extends SysuiTestCase {
     private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
     private static final String SETTING = QSHost.TILES_SETTING;
 
-    @Mock
-    private QSFactory mDefaultFactory;
     @Mock
     private PluginManager mPluginManager;
     @Mock
@@ -117,7 +118,6 @@ public class QSTileHostTest extends SysuiTestCase {
     private CustomTile mCustomTile;
     @Mock
     private UserTracker mUserTracker;
-    private SecureSettings mSecureSettings;
     @Mock
     private CustomTileStatePersister mCustomTileStatePersister;
     @Mock
@@ -127,6 +127,10 @@ public class QSTileHostTest extends SysuiTestCase {
     @Mock
     private UserFileManager mUserFileManager;
 
+    private SecureSettings mSecureSettings;
+
+    private QSFactory mDefaultFactory;
+
     private SparseArray<SharedPreferences> mSharedPreferencesByUser;
 
     private FakeFeatureFlags mFeatureFlags;
@@ -144,6 +148,8 @@ public class QSTileHostTest extends SysuiTestCase {
 
         mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
         mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
+        // TODO(b/299909337): Add test checking the new factory is used when the flag is on
+        mFeatureFlags.set(Flags.QS_PIPELINE_NEW_TILES, false);
         mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags);
 
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
@@ -164,7 +170,8 @@ public class QSTileHostTest extends SysuiTestCase {
 
         mSecureSettings = new FakeSettings();
         saveSetting("");
-        mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
+        setUpTileFactory();
+        mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor,
                 mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
                 mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
@@ -178,7 +185,6 @@ public class QSTileHostTest extends SysuiTestCase {
                 mMainExecutor.runAllReady();
             }
         }, mUserTracker.getUserId());
-        setUpTileFactory();
     }
 
     private void saveSetting(String value) {
@@ -191,32 +197,29 @@ public class QSTileHostTest extends SysuiTestCase {
     }
 
     private void setUpTileFactory() {
-        // Only create this kind of tiles
-        when(mDefaultFactory.createTile(anyString())).thenAnswer(
-                invocation -> {
-                    String spec = invocation.getArgument(0);
-                    if ("spec1".equals(spec)) {
-                        return new TestTile1(mQSTileHost);
-                    } else if ("spec2".equals(spec)) {
-                        return new TestTile2(mQSTileHost);
-                    } else if ("spec3".equals(spec)) {
-                        return new TestTile3(mQSTileHost);
-                    } else if ("na".equals(spec)) {
-                        return new NotAvailableTile(mQSTileHost);
-                    } else if (CUSTOM_TILE_SPEC.equals(spec)) {
-                        QSTile tile = mCustomTile;
-                        QSTile.State s = mock(QSTile.State.class);
-                        s.spec = spec;
-                        when(mCustomTile.getState()).thenReturn(s);
-                        return tile;
-                    } else if ("internet".equals(spec)
-                            || "wifi".equals(spec)
-                            || "cell".equals(spec)) {
-                        return new TestTile1(mQSTileHost);
-                    } else {
-                        return null;
-                    }
-                });
+        mDefaultFactory = new FakeQSFactory(spec -> {
+            if ("spec1".equals(spec)) {
+                return new TestTile1(mQSTileHost);
+            } else if ("spec2".equals(spec)) {
+                return new TestTile2(mQSTileHost);
+            } else if ("spec3".equals(spec)) {
+                return new TestTile3(mQSTileHost);
+            } else if ("na".equals(spec)) {
+                return new NotAvailableTile(mQSTileHost);
+            } else if (CUSTOM_TILE_SPEC.equals(spec)) {
+                QSTile tile = mCustomTile;
+                QSTile.State s = mock(QSTile.State.class);
+                s.spec = spec;
+                when(mCustomTile.getState()).thenReturn(s);
+                return tile;
+            } else if ("internet".equals(spec)
+                    || "wifi".equals(spec)
+                    || "cell".equals(spec)) {
+                return new TestTile1(mQSTileHost);
+            } else {
+                return null;
+            }
+        });
         when(mCustomTile.isAvailable()).thenReturn(true);
     }
 
@@ -703,7 +706,7 @@ public class QSTileHostTest extends SysuiTestCase {
     }
 
     private class TestQSTileHost extends QSTileHost {
-        TestQSTileHost(Context context,
+        TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider,
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles,
@@ -712,7 +715,7 @@ public class QSTileHostTest extends SysuiTestCase {
                 CustomTileStatePersister customTileStatePersister,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
-            super(context, defaultFactory, mainExecutor, pluginManager,
+            super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
                     tunerService, autoTiles,  shadeController, qsLogger,
                     userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index bde30382ba05d988d1fd44ecae44c1817f350f37..d3cd26bcd3cb9d8bca4d794c16f675fb5cc4ed38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -124,6 +124,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
                     if (FACTORY_TILES.contains(spec)) {
                         FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor);
                         tile.setState(mState);
+                        tile.setTileSpec(spec);
                         return tile;
                     } else {
                         return null;
@@ -284,7 +285,10 @@ public class TileQueryHelperTest extends SysuiTestCase {
         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
 
         QSTile t = mock(QSTile.class);
-        when(mQSHost.createTile("hotspot")).thenReturn(t);
+        when(mQSHost.createTile("hotspot")).thenAnswer(invocation -> {
+            t.setTileSpec("hotspot");
+            return t;
+        });
 
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 "hotspot");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index dc1b9c4d0d14864c13e0fada8f55631f4e1a9020..a7505240caeb704977cb9fb3a71846786fd07cb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -46,6 +46,7 @@ import com.android.systemui.qs.pipeline.domain.model.TileModel
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
 import com.android.systemui.qs.toProto
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.FakeUserRepository
@@ -91,6 +92,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
 
     @Mock private lateinit var logger: QSPipelineLogger
 
+    @Mock private lateinit var newQSTileFactory: NewQSTileFactory
+
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -105,6 +108,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
 
         featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
         featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
+        // TODO(b/299909337): Add test checking the new factory is used when the flag is on
+        featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
 
@@ -117,6 +122,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
                 userRepository = userRepository,
                 customTileStatePersister = customTileStatePersister,
                 tileFactory = tileFactory,
+                newQSTileFactory = { newQSTileFactory },
                 customTileAddedRepository = customTileAddedRepository,
                 tileLifecycleManagerFactory = tileLifecycleManagerFactory,
                 userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
index 5630b9d3b2929c8c1a894eef4e03602246b1c00a..2e6b50b637dd1b978cff5de36866d129c924a2a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
@@ -1,6 +1,6 @@
 package com.android.systemui.qs.pipeline.domain.interactor
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
@@ -21,7 +21,7 @@ import org.junit.runner.RunWith
 import org.mockito.MockitoAnnotations
 
 @RoboPilotTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class RestoreReconciliationInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
index 47b4244e0910eda49aeefa386feef04ebfa2dd7e..077c81343c83b1aedf8d85e90bfd7d5b87ff94ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
@@ -6,7 +6,6 @@ import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,7 +33,7 @@ class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
     fun testPassesIntentToStarter() {
         val intent = Intent("test.ACTION")
 
-        underTest.handle(QSTileUserAction.Click(context, null), intent)
+        underTest.handle(null, intent)
 
         verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 643866e3cadeb891e47f549b550db07cb05c24ff..eacb08010159255567868eb1cc934fd6d8fcafc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -1,11 +1,14 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.graphics.drawable.Icon
+import android.graphics.drawable.ShapeDrawable
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.MediumTest
+import com.android.internal.logging.InstanceId
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
@@ -71,20 +74,21 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
                 fakeQSTileUserActionInteractor,
                 fakeQSTileDataInteractor,
                 object : QSTileDataToStateMapper<Any> {
-                    override fun map(config: QSTileConfig, data: Any): QSTileState {
-                        return QSTileState(config.tileIcon, config.tileLabel)
-                    }
+                    override fun map(config: QSTileConfig, data: Any): QSTileState =
+                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
                 },
                 testCoroutineDispatcher,
                 tileScope = scope.backgroundScope,
             ) {}
 
     private companion object {
+
         val TEST_QS_TILE_CONFIG =
             QSTileConfig(
                 TileSpec.create("default"),
-                Icon.createWithContentUri(""),
-                "",
+                Icon.Loaded(ShapeDrawable(), null),
+                0,
+                InstanceId.fakeInstanceId(0),
             )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 59b59539374900969f0a18161e855abc108b4fde..a2aed988a423b01743d6fd9e2a3b108710cba86a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -41,7 +41,7 @@ import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaProjectionCaptureTarget;
+import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index d470d24489af1d2856d95cbf8acff3b20119ef19..3ae1f35b8134e0866bd3379f39e544fdb77f967c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -22,11 +22,13 @@ import android.testing.TestableLooper
 import android.view.View
 import android.widget.Spinner
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
+import com.android.systemui.mediaprojection.permission.SINGLE_APP
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -35,8 +37,8 @@ import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 1592c3007ec837265ed966ae0a3ea4282ceadf82..a6180ec8ea0eecbfa170a3cd903cdf843ec101f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -248,7 +248,7 @@ public class StatusBarIconViewTest extends SysuiTestCase {
     }
 
     @Test
-    public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+    public void testUpdateIconScale_smallerFontAndRawDrawableSizeLessThanDpIconSize() {
         int dpIconSize = 60;
         int dpDrawingSize = 30;
         // smaller font scaling causes the spIconSize < dpIconSize
@@ -262,12 +262,42 @@ public class StatusBarIconViewTest extends SysuiTestCase {
         setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
         mIconView.maybeUpdateIconScaleDimens();
 
-        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // WHEN both the raw/constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled up from constrained drawable size to the raw drawable size
+        float scaleToBackRawDrawableSize = (float) 50 / 40;
         // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
         float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
         // THEN the scaled icon should be scaled down further to fit spIconSize
         float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
-        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+        assertEquals(scaleToBackRawDrawableSize * scaleToFitDrawingSize * scaleToFitSpIconSize,
+                mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // smaller font scaling causes the spIconSize < dpIconSize
+        int spIconSize = 40;
+        // the icon view layout size would be 40x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 70x70. When put the drawable into iconView whose
+        // layout size is 40x150, the drawable size would be constrained to 40x40
+        setIconDrawableWithSize(/* width= */ 70, /* height= */ 70);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN the raw drawable width/height are larger than dpIconSize,
+        //      but the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled up from constrained drawable size to fit dpIconSize
+        float scaleToFitDpIconSize = (float) dpIconSize / 40;
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        // THEN the scaled icon should be scaled down further to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDpIconSize * scaleToFitDrawingSize * scaleToFitSpIconSize,
+                mIconView.getIconScale(), 0.01f);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 15b9d61fc96bdc3b1f55bc76d784a0902b9651e4..c935dbb0ca1c5aceb1836c456b8bf784949f56b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -17,13 +17,14 @@
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
@@ -181,6 +182,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -192,6 +195,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -203,6 +208,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -214,6 +221,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -225,6 +234,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -236,6 +247,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -247,6 +260,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
@@ -258,6 +273,8 @@ class InternetTileViewModelTest : SysuiTestCase() {
 
             assertThat(latest?.icon)
                 .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+            assertThat(latest?.stateDescription.loadContentDescription(context))
+                .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index a520f6c109cca01e32fb58d5efc0cf03b839d147..49a2648a7caca2d93792065cd47d0c8a1455b640 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiIcons
@@ -136,6 +138,10 @@ class WifiViewModelTest : SysuiTestCase() {
             // is used instead
             assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java)
             assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1])
+            assertThat(
+                    (latest as WifiIcon.Visible).contentDescription.loadContentDescription(context)
+                )
+                .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
index bf26e719433db3c555589a89f47e767dae71f295..cbf4ae5e301497436207afbfa4af1f784b059cf4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
@@ -21,6 +21,6 @@ import com.android.systemui.plugins.qs.QSTile
 
 class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory {
     override fun createTile(tileSpec: String): QSTile? {
-        return tileCreator(tileSpec)
+        return tileCreator(tileSpec)?.also { it.tileSpec = tileSpec }
     }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index b56b47f9c727225542533c304c327d9a1b0a6fad..9dd0dca47f0efca42a7bf2ec25db3c7a27940dc0 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -121,7 +121,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
 
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
-    final ArraySet<Integer> mRunningUids = new ArraySet<>();
+    private final ArraySet<Integer> mRunningUids = new ArraySet<>();
     @Nullable private final ActivityListener mActivityListener;
     @Nullable private final PipBlockedCallback mPipBlockedCallback;
     @Nullable private final IntentListenerCallback mIntentListenerCallback;
diff --git a/services/core/java/com/android/server/connectivity/OWNERS b/services/core/java/com/android/server/connectivity/OWNERS
index 62c5737a2e8e5a7da8dcff019eb36d3c55b94ce4..c24680e9b06a616db26148e4d2ce2e833626daf9 100644
--- a/services/core/java/com/android/server/connectivity/OWNERS
+++ b/services/core/java/com/android/server/connectivity/OWNERS
@@ -1,2 +1,2 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index debf828abf0ab38ea95296a5c560cde3af2130c2..99a5398aa7eec4cf16a72e3af2cfe894078a0897 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -33,7 +33,6 @@ import android.util.Spline;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.display.BrightnessUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
diff --git a/core/java/com/android/internal/display/BrightnessUtils.java b/services/core/java/com/android/server/display/BrightnessUtils.java
similarity index 97%
rename from core/java/com/android/internal/display/BrightnessUtils.java
rename to services/core/java/com/android/server/display/BrightnessUtils.java
index 82b506bed80bce18fafdfb7b50c4c8159c14d57f..84fa0cccbd10b7dd57f883b09018f0f574c52953 100644
--- a/core/java/com/android/internal/display/BrightnessUtils.java
+++ b/services/core/java/com/android/server/display/BrightnessUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.display;
+package com.android.server.display;
 
 import android.util.MathUtils;
 
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index e38c2c58f453c0eb776b3cd7f36316dd67431add..5ba042c51a452c4f754b8885960348ab1a626f0f 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,8 +20,6 @@ import android.animation.ValueAnimator;
 import android.util.FloatProperty;
 import android.view.Choreographer;
 
-import com.android.internal.display.BrightnessUtils;
-
 /**
  * A custom animator that progressively updates a property value at
  * a given variable rate until it reaches a particular target value.
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 41053e908a00a3714398005296c7db8d7f7070ef..68848a2ad426c1e3a046d7e6752172c39e67be51 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -16,35 +16,65 @@
 
 package com.android.server.grammaticalinflection;
 
+import static android.app.Flags.systemTermsOfAddressEnabled;
 import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 
 import android.annotation.Nullable;
+import android.app.GrammaticalInflectionManager;
 import android.app.IGrammaticalInflectionManager;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
-import android.os.IBinder;
+import android.os.Environment;
 import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.SystemProperties;
+import android.util.AtomicFile;
 import android.util.Log;
+import android.util.SparseIntArray;
+import android.util.Xml;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
 /**
  * The implementation of IGrammaticalInflectionManager.aidl.
  *
  * <p>This service is API entry point for storing app-specific grammatical inflection.
  */
 public class GrammaticalInflectionService extends SystemService {
-    private final String TAG = "GrammaticalInflection";
+    private static final String TAG = "GrammaticalInflection";
+    private static final String ATTR_NAME = "grammatical_gender";
+    private static final String USER_SETTINGS_FILE_NAME = "user_settings.xml";
+    private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection";
+    private static final String GRAMMATICAL_INFLECTION_ENABLED =
+            "i18n.grammatical_Inflection.enabled";
+
     private final GrammaticalInflectionBackupHelper mBackupHelper;
     private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    private final Object mLock = new Object();
+    private final SparseIntArray mGrammaticalGenderCache = new SparseIntArray();
+
     private PackageManagerInternal mPackageManagerInternal;
-    private static final String GRAMMATICAL_INFLECTION_ENABLED =
-            "i18n.grammatical_Inflection.enabled";
+    private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
 
     /**
      * Initializes the system service.
@@ -62,22 +92,46 @@ public class GrammaticalInflectionService extends SystemService {
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mBackupHelper = new GrammaticalInflectionBackupHelper(
                 this, context.getPackageManager());
+        mBinderService = new GrammaticalInflectionBinderService();
     }
 
     @Override
     public void onStart() {
-        publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+        publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mBinderService);
         LocalServices.addService(GrammaticalInflectionManagerInternal.class,
                 new GrammaticalInflectionManagerInternalImpl());
     }
 
-    private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+    private final class GrammaticalInflectionBinderService extends
+            IGrammaticalInflectionManager.Stub {
         @Override
         public void setRequestedApplicationGrammaticalGender(
                 String appPackageName, int userId, int gender) {
             GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
                     appPackageName, userId, gender);
         }
+
+        @Override
+        public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+            checkCallerIsSystem();
+            checkSystemTermsOfAddressIsEnabled();
+            GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
+                    userId);
+        }
+
+        @Override
+        public int getSystemGrammaticalGender(int userId) {
+            checkSystemTermsOfAddressIsEnabled();
+            return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+        }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err, String[] args, ShellCallback callback,
+                ResultReceiver resultReceiver) {
+            (new GrammaticalInflectionShellCommand(mBinderService))
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
     };
 
     private final class GrammaticalInflectionManagerInternalImpl
@@ -94,12 +148,6 @@ public class GrammaticalInflectionService extends SystemService {
         public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
             mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
         }
-
-        private void checkCallerIsSystem() {
-            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-                throw new SecurityException("Caller is not system.");
-            }
-        }
     }
 
     protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -137,4 +185,105 @@ public class GrammaticalInflectionService extends SystemService {
 
         updater.setGrammaticalGender(gender).commit();
     }
+
+    protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
+        if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+                grammaticalGender)) {
+            throw new IllegalArgumentException("Unknown grammatical gender");
+        }
+
+        synchronized (mLock) {
+            final File file = getGrammaticalGenderFile(userId);
+            final AtomicFile atomicFile = new AtomicFile(file);
+            FileOutputStream stream = null;
+            try {
+                stream = atomicFile.startWrite();
+                stream.write(toXmlByteArray(grammaticalGender, stream));
+                atomicFile.finishWrite(stream);
+                mGrammaticalGenderCache.put(userId, grammaticalGender);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to write file " + atomicFile, e);
+                if (stream != null) {
+                    atomicFile.failWrite(stream);
+                }
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
+    //  gender.
+    public int getSystemGrammaticalGender(int userId) {
+        synchronized (mLock) {
+            final File file = getGrammaticalGenderFile(userId);
+            if (!file.exists()) {
+                Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+                return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+            }
+
+            if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
+                try {
+                    InputStream in = new FileInputStream(file);
+                    final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                    mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+                } catch (IOException | XmlPullParserException e) {
+                    Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+                }
+            }
+            return mGrammaticalGenderCache.get(userId);
+        }
+    }
+
+    private File getGrammaticalGenderFile(int userId) {
+        final File dir = new File(Environment.getDataSystemCeDirectory(userId),
+                TAG_GRAMMATICAL_INFLECTION);
+        return new File(dir, USER_SETTINGS_FILE_NAME);
+    }
+
+    private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
+
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+            out.startDocument(/* encoding= */ null, /* standalone= */ true);
+            out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+            out.attributeInt(null, ATTR_NAME, grammaticalGender);
+            out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+            out.endDocument();
+
+            return outputStream.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+
+        XmlUtils.nextElement(parser);
+        while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+            String tagName = parser.getName();
+            if (TAG_GRAMMATICAL_INFLECTION.equals(tagName)) {
+                return parser.getAttributeInt(null, ATTR_NAME);
+            } else {
+                XmlUtils.nextElement(parser);
+            }
+        }
+
+        return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+    }
+
+    private void checkCallerIsSystem() {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) {
+            throw new SecurityException("Caller is not system and shell.");
+        }
+    }
+
+    private void checkSystemTermsOfAddressIsEnabled() {
+        if (!systemTermsOfAddressEnabled()) {
+            throw new RuntimeException("The flag must be enabled to allow calling the API.");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..d22372860eadd88e5be58982c2a280a7c50d3bea
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.ActivityManager;
+import android.app.GrammaticalInflectionManager;
+import android.app.IGrammaticalInflectionManager;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell commands for {@link GrammaticalInflectionService}
+ */
+class GrammaticalInflectionShellCommand extends ShellCommand {
+
+    private static final SparseArray<String> GRAMMATICAL_GENDER_MAP = new SparseArray<>();
+    static {
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+                "Not specified (0)");
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NEUTRAL, "Neuter (1)");
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_FEMININE, "Feminine (2)");
+        GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_MASCULINE, "Masculine (3)");
+    }
+
+    private final IGrammaticalInflectionManager mBinderService;
+
+    GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+        mBinderService = grammaticalInflectionManager;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        switch (cmd) {
+            case "set-system-grammatical-gender":
+                return runSetSystemWideGrammaticalGender();
+            case "get-system-grammatical-gender":
+                return runGetSystemGrammaticalGender();
+            default: {
+                return handleDefaultCommands(cmd);
+            }
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Grammatical inflection manager (grammatical_inflection) shell commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println(
+                "  set-system-grammatical-gender [--user <USER_ID>] [--grammaticalGender "
+                        + "<GRAMMATICAL_GENDER>]");
+        pw.println("      Set the system grammatical gender for system.");
+        pw.println("      --user <USER_ID>: apply for the given user, "
+                + "the current user is used when unspecified.");
+        pw.println(
+                "      --grammaticalGender <GRAMMATICAL_GENDER>: The terms of address the user "
+                        + "preferred in system, not specified (0) is used when unspecified.");
+        pw.println(
+                "                 eg. 0 = not_specified, 1 = neuter, 2 = feminine, 3 = masculine"
+                        + ".");
+        pw.println(
+                "  get-system-grammatical-gender [--user <USER_ID>]");
+        pw.println("      Get the system grammatical gender for system.");
+        pw.println("      --user <USER_ID>: apply for the given user, "
+                + "the current user is used when unspecified.");
+    }
+
+    private int runSetSystemWideGrammaticalGender() {
+        int userId = ActivityManager.getCurrentUser();
+        int grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        do {
+            String option = getNextOption();
+            if (option == null) {
+                break;
+            }
+            switch (option) {
+                case "--user": {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                }
+                case "-g":
+                case "--grammaticalGender": {
+                    grammaticalGender = parseGrammaticalGender();
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + option);
+                }
+            }
+        } while (true);
+
+        try {
+            mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+        } catch (RemoteException e) {
+            getOutPrintWriter().println("Remote Exception: " + e);
+        }
+        return 0;
+    }
+
+    private int runGetSystemGrammaticalGender() {
+        int userId = ActivityManager.getCurrentUser();
+        do {
+            String option = getNextOption();
+            if (option == null) {
+                break;
+            }
+            switch (option) {
+                case "--user": {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + option);
+                }
+            }
+        } while (true);
+
+        try {
+            int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+            getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
+        } catch (RemoteException e) {
+            getOutPrintWriter().println("Remote Exception: " + e);
+        }
+        return 0;
+    }
+
+    private int parseGrammaticalGender() {
+        String arg = getNextArg();
+        if (arg == null) {
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        } else {
+            int grammaticalGender = Integer.parseInt(arg);
+            if (GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+                    grammaticalGender)) {
+                return grammaticalGender;
+            } else {
+                return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS
index 5f16ba9123b7737342770ac6ec6f8ee3646e39f5..41d079ed9e75ab1636982d7bf87fe52a18ebce5e 100644
--- a/services/core/java/com/android/server/grammaticalinflection/OWNERS
+++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS
@@ -2,3 +2,4 @@
 allenwtsu@google.com
 goldmanj@google.com
 calvinpan@google.com
+zoeychen@google.com
diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS
index 9c96d46f15b8c3be86ad009cf7f8a51d7be733bc..d0e95dd55b6c4dc448cdf669f3bad37f38c5db5a 100644
--- a/services/core/java/com/android/server/net/OWNERS
+++ b/services/core/java/com/android/server/net/OWNERS
@@ -1,5 +1,5 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
 
 jsharkey@android.com
 sudheersai@google.com
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 04d1da61f3cc77d987b76f46970828b9e2effd4d..390c45b6a524026eb6a2557901bd4f8dd62c9472 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -145,6 +145,19 @@
           "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
         }
       ]
+    },
+    {
+      "file_patterns": [
+        "(/|^)InstallPackageHelper\\.java",
+        "services/core/java/com/android/server/pm/parsing/.*",
+        "services/core/java/com/android/server/pm/pkg/parsing/.*"
+      ],
+      "name": "SdkSandboxManagerServiceUnitTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ],
   "imports": [
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dfc9b8b030f2fcd0869979aa44cf63b7e1bc7f43..097656cac7f7b135cbb2f0312754764dcd5e8257 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -198,7 +198,6 @@ import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
-import com.android.internal.display.BrightnessUtils;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -218,6 +217,7 @@ import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
+import com.android.server.display.BrightnessUtils;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.input.KeyboardMetricsCollector;
 import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
diff --git a/services/core/java/com/android/server/security/rkp/OWNERS b/services/core/java/com/android/server/security/rkp/OWNERS
index 348f940483111b84262f9522284b6b02ba23c1e9..ea6dc727c7b2d3677266288ee4c835f012a5e424 100644
--- a/services/core/java/com/android/server/security/rkp/OWNERS
+++ b/services/core/java/com/android/server/security/rkp/OWNERS
@@ -1 +1 @@
-file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS
+file:platform/frameworks/base:main:/core/java/android/security/rkp/OWNERS
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 16847243b6e15602cac184139c27541d70985b45..ed10346d8495be606e458c531eb4a7fc0484a2f6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5366,7 +5366,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                 // Finish should only ever commit visibility=false, so we can check full containment
                 // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
-                if (!inFinishingTransition && !mDisplayContent.isSleeping()) {
+                if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) {
                     Slog.e(TAG, "setVisibility=" + visible
                             + " while transition is not collecting or finishing "
                             + this + " caller=" + Debug.getCallers(8));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d95bc77edcbccf4e2a61ab998aaff82ae8944ed..50bc825d8beac263ef87314977eaef188d9b2469 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -254,6 +254,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
      */
     boolean mClearedForReorderActivityToFront;
 
+    /**
+     * Whether the TaskFragment surface is managed by a system {@link TaskFragmentOrganizer}.
+     */
+    boolean mIsSurfaceManagedBySystemOrganizer = false;
+
     /**
      * When we are in the process of pausing an activity, before starting the
      * next one, this variable holds the activity that is currently being paused.
@@ -449,13 +454,21 @@ class TaskFragment extends WindowContainer<WindowContainer> {
 
     void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
             @NonNull String processName) {
+        setTaskFragmentOrganizer(organizer, uid, processName,
+                false /* isSurfaceManagedBySystemOrganizer */);
+    }
+
+    void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
+            @NonNull String processName, boolean isSurfaceManagedBySystemOrganizer) {
         mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
         mTaskFragmentOrganizerUid = uid;
         mTaskFragmentOrganizerProcessName = processName;
+        mIsSurfaceManagedBySystemOrganizer = isSurfaceManagedBySystemOrganizer;
     }
 
     void onTaskFragmentOrganizerRemoved() {
         mTaskFragmentOrganizer = null;
+        mIsSurfaceManagedBySystemOrganizer = false;
     }
 
     /** Whether this TaskFragment is organized by the given {@code organizer}. */
@@ -2396,6 +2409,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
         if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
             return;
         }
+        if (mIsSurfaceManagedBySystemOrganizer) {
+            return;
+        }
         if (mTransitionController.isShellTransitionsEnabled()
                 && !mTransitionController.isCollecting(this)) {
             // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index ea722b61be6fc85dc014fb187ef4f3f121d79d50..04164c20a3725ad638b3122d10af92ab969b19b1 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -26,6 +26,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_I
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
 
@@ -50,12 +51,15 @@ import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
 import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -132,6 +136,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
         private final Map<IBinder, ActivityRecord> mTemporaryActivityTokens =
                 new WeakHashMap<>();
 
+        /**
+         * Whether this {@link android.window.TaskFragmentOrganizer} is a system organizer. If true,
+         * the {@link android.view.SurfaceControl} of the {@link TaskFragment} is provided to the
+         * client in the {@link TYPE_TASK_FRAGMENT_APPEARED} event.
+         */
+        private final boolean mIsSystemOrganizer;
+
         /**
          * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
          * organized by this organizer.
@@ -147,10 +158,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
          */
         private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
 
-        TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) {
+        TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
+                boolean isSystemOrganizer) {
             mOrganizer = organizer;
             mOrganizerPid = pid;
             mOrganizerUid = uid;
+            mIsSystemOrganizer = isSystemOrganizer;
             try {
                 mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/);
             } catch (RemoteException e) {
@@ -235,11 +248,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
             tf.mTaskFragmentAppearedSent = true;
             mLastSentTaskFragmentInfos.put(tf, info);
             mTaskFragmentTaskIds.put(tf, taskId);
-            return new TaskFragmentTransaction.Change(
+            final TaskFragmentTransaction.Change change = new TaskFragmentTransaction.Change(
                     TYPE_TASK_FRAGMENT_APPEARED)
                     .setTaskFragmentToken(tf.getFragmentToken())
                     .setTaskFragmentInfo(info)
                     .setTaskId(taskId);
+            if (mIsSystemOrganizer) {
+                change.setTaskFragmentSurfaceControl(tf.getSurfaceControl());
+            }
+            return change;
         }
 
         @NonNull
@@ -435,8 +452,25 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
                 : null;
     }
 
+    @VisibleForTesting
+    void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+        registerOrganizerInternal(organizer, false /* isSystemOrganizer */);
+    }
+
     @Override
-    public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+    public void registerOrganizer(
+            @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+        registerOrganizerInternal(
+                organizer,
+                Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer);
+    }
+
+    @VisibleForTesting
+    void registerOrganizerInternal(
+            @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
+        if (isSystemOrganizer) {
+            enforceTaskPermission("registerSystemOrganizer()");
+        }
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -448,7 +482,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
                         "Replacing existing organizer currently unsupported");
             }
             mTaskFragmentOrganizerState.put(organizer.asBinder(),
-                    new TaskFragmentOrganizerState(organizer, pid, uid));
+                    new TaskFragmentOrganizerState(organizer, pid, uid, isSystemOrganizer));
             mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>());
         }
     }
@@ -711,6 +745,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
         }
     }
 
+    boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+        final TaskFragmentOrganizerState state =
+                mTaskFragmentOrganizerState.get(token.asBinder());
+        return state != null && state.mIsSystemOrganizer;
+    }
+
     @Nullable
     private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
             @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6d7e2970e2b1f1e8e79bba6fa9135a4e1eaebfb0..376cad7348662aef6d59917dd3e53cbcd78fd793 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -99,6 +99,7 @@ import android.window.IWindowOrganizerController;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizerToken;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -1993,8 +1994,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
                 creationParams.getFragmentToken(), true /* createdByOrganizer */);
         // Set task fragment organizer immediately, since it might have to be notified about further
         // actions.
-        taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
-                ownerActivity.getUid(), ownerActivity.info.processName);
+        TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
+        taskFragment.setTaskFragmentOrganizer(organizerToken,
+                ownerActivity.getUid(), ownerActivity.info.processName,
+                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
         final int position;
         if (creationParams.getPairedPrimaryFragmentToken() != null) {
             // When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/net/OWNERS b/services/net/OWNERS
index 62c5737a2e8e5a7da8dcff019eb36d3c55b94ce4..c24680e9b06a616db26148e4d2ce2e833626daf9 100644
--- a/services/net/OWNERS
+++ b/services/net/OWNERS
@@ -1,2 +1,2 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/services/tests/RemoteProvisioningServiceTests/OWNERS b/services/tests/RemoteProvisioningServiceTests/OWNERS
index 348f940483111b84262f9522284b6b02ba23c1e9..ea6dc727c7b2d3677266288ee4c835f012a5e424 100644
--- a/services/tests/RemoteProvisioningServiceTests/OWNERS
+++ b/services/tests/RemoteProvisioningServiceTests/OWNERS
@@ -1 +1 @@
-file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS
+file:platform/frameworks/base:main:/core/java/android/security/rkp/OWNERS
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
index 7a4327cc05980c655a600acff492190f7e014284..2fd6e5fb68926995927b2df35b4cd4270c145e09 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
@@ -19,7 +19,6 @@ package com.android.server.display;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.isA;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -28,18 +27,15 @@ import static org.mockito.Mockito.when;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.database.ContentObserver;
-import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
 import android.os.Handler;
-import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.view.Display;
-import android.view.DisplayAdjustments;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
@@ -63,7 +59,6 @@ public class BrightnessSynchronizerTest {
     private static final float EPSILON = 0.00001f;
     private static final Uri BRIGHTNESS_URI =
             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
-    private static final float BRIGHTNESS_MAX = 0.6f;
 
     private Context mContext;
     private MockContentResolver mContentResolverSpy;
@@ -71,7 +66,6 @@ public class BrightnessSynchronizerTest {
     private DisplayListener mDisplayListener;
     private ContentObserver mContentObserver;
     private TestLooper mTestLooper;
-    private BrightnessSynchronizer mSynchronizer;
 
     @Mock private DisplayManager mDisplayManagerMock;
     @Captor private ArgumentCaptor<DisplayListener> mDisplayListenerCaptor;
@@ -80,17 +74,7 @@ public class BrightnessSynchronizerTest {
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-
-        Display display = mock(Display.class);
-        when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
-        BrightnessInfo info = new BrightnessInfo(PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                PowerManager.BRIGHTNESS_MIN, BRIGHTNESS_MAX,
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, BRIGHTNESS_MAX,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-        when(display.getBrightnessInfo()).thenReturn(info);
-
-        mContext = spy(new ContextWrapper(
-                ApplicationProvider.getApplicationContext().createDisplayContext(display)));
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mContentResolverSpy = spy(new MockContentResolver(mContext));
         mContentResolverSpy.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContext.getContentResolver()).thenReturn(mContentResolverSpy);
@@ -144,12 +128,13 @@ public class BrightnessSynchronizerTest {
     @Test
     public void testSetSameIntValue_nothingUpdated() {
         putFloatSetting(0.5f);
+        putIntSetting(128);
         start();
 
-        putIntSetting(fToI(0.5f));
+        putIntSetting(128);
         advanceTime(10);
         verify(mDisplayManagerMock, times(0)).setBrightness(
-                eq(Display.DEFAULT_DISPLAY), eq(0.5f));
+                eq(Display.DEFAULT_DISPLAY), eq(iToF(128)));
     }
 
     @Test
@@ -169,13 +154,14 @@ public class BrightnessSynchronizerTest {
         // Verify that this update did not get sent to float, because synchronizer
         // is still waiting for confirmation of its first value.
         verify(mDisplayManagerMock, times(0)).setBrightness(
-                Display.DEFAULT_DISPLAY, iToF(20));
+                eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
 
         // Send the confirmation of the initial change. This should trigger the new value to
         // finally be processed and we can verify that the new value (20) is sent.
         putIntSetting(fToI(0.4f));
         advanceTime(10);
-        verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20));
+        verify(mDisplayManagerMock).setBrightness(
+                eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
 
     }
 
@@ -197,7 +183,8 @@ public class BrightnessSynchronizerTest {
         advanceTime(200);
 
         // Verify that the new value gets sent because the timeout expired.
-        verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20));
+        verify(mDisplayManagerMock).setBrightness(
+                eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
 
         // Send a confirmation of the initial event, BrightnessSynchronizer should treat this as a
         // new event because the timeout had already expired
@@ -209,14 +196,14 @@ public class BrightnessSynchronizerTest {
 
         // Verify we sent what would have been the confirmation as a new event to displaymanager.
         // We do both fToI and iToF because the conversions are not symmetric.
-        verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY,
-                iToF(fToI(0.4f)));
+        verify(mDisplayManagerMock).setBrightness(
+                eq(Display.DEFAULT_DISPLAY), eq(iToF(fToI(0.4f))));
     }
 
-    private void start() {
-        mSynchronizer = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
+    private BrightnessSynchronizer start() {
+        BrightnessSynchronizer bs = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
                 mClock::now);
-        mSynchronizer.startSynchronizing();
+        bs.startSynchronizing();
         verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
                 isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
         mDisplayListener = mDisplayListenerCaptor.getValue();
@@ -224,6 +211,7 @@ public class BrightnessSynchronizerTest {
         verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
                 mContentObserverCaptor.capture(), eq(UserHandle.USER_ALL));
         mContentObserver = mContentObserverCaptor.getValue();
+        return bs;
     }
 
     private int getIntSetting() throws Exception {
@@ -253,11 +241,11 @@ public class BrightnessSynchronizerTest {
     }
 
     private int fToI(float brightness) {
-        return mSynchronizer.brightnessFloatToIntSetting(brightness);
+        return BrightnessSynchronizer.brightnessFloatToInt(brightness);
     }
 
     private float iToF(int brightness) {
-        return mSynchronizer.brightnessIntSettingToFloat(brightness);
+        return BrightnessSynchronizer.brightnessIntToFloat(brightness);
     }
 
     private void advanceTime(long timeMs) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 306de525e53cdffdcdd6199a3f103f8f6d5f0119..2396905aecbfdaba16683ab0a3add0de816e3166 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -71,7 +71,6 @@ import android.graphics.Rect;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
@@ -95,7 +94,6 @@ import android.os.Process;
 import android.os.RemoteException;
 import android.view.ContentRecordingSession;
 import android.view.Display;
-import android.view.DisplayAdjustments;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
 import android.view.DisplayInfo;
@@ -103,6 +101,7 @@ import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.DisplayWindowPolicyController;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
@@ -333,11 +332,7 @@ public class DisplayManagerServiceTest {
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
         // TODO: b/287945043
-        Display display = mock(Display.class);
-        when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
-        when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
-        mContext = spy(new ContextWrapper(
-                ApplicationProvider.getApplicationContext().createDisplayContext(display)));
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResources = Mockito.spy(mContext.getResources());
         manageDisplaysPermission(/* granted= */ false);
         when(mContext.getResources()).thenReturn(mResources);
@@ -1912,6 +1907,7 @@ public class DisplayManagerServiceTest {
 
     @Test
     public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
+        Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
 
         // get the first two internal displays
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7c8a6cee0d9d357ac43b0b6987ed58817574768
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.WindowConfiguration;
+import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArraySet;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.app.BlockedAppStreamingActivity;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class GenericWindowPolicyControllerTest {
+
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+    private static final int TEST_UID = 1234567;
+    private static final String DISPLAY_CATEGORY = "com.display.category";
+    private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp";
+    private static final String BLOCKED_PACKAGE_NAME = "com.blockedapp";
+    private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+    private static final String TEST_SITE = "http://test";
+    private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT =
+            new ComponentName("android", BlockedAppStreamingActivity.class.getName());
+    private static final ComponentName BLOCKED_COMPONENT = new ComponentName(BLOCKED_PACKAGE_NAME,
+            BLOCKED_PACKAGE_NAME);
+    private static final ComponentName NONBLOCKED_COMPONENT = new ComponentName(
+            NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME);
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private GenericWindowPolicyController.PipBlockedCallback mPipBlockedCallback;
+    @Mock
+    private VirtualDeviceManager.ActivityListener mActivityListener;
+    @Mock
+    private GenericWindowPolicyController.IntentListenerCallback mIntentListenerCallback;
+    @Mock
+    private GenericWindowPolicyController.ActivityBlockedCallback mActivityBlockedCallback;
+    @Mock
+    private GenericWindowPolicyController.RunningAppsChangedListener mRunningAppsChangedListener;
+    @Mock
+    private GenericWindowPolicyController.SecureWindowCallback mSecureWindowCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void showTasksInHostDeviceRecents() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.setShowInHostDeviceRecents(true);
+        assertThat(gwpc.canShowTasksInHostDeviceRecents()).isTrue();
+
+        gwpc.setShowInHostDeviceRecents(false);
+        assertThat(gwpc.canShowTasksInHostDeviceRecents()).isFalse();
+    }
+
+    @Test
+    public void containsUid() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        assertThat(gwpc.containsUid(TEST_UID)).isFalse();
+
+        gwpc.onRunningAppsChanged(new ArraySet<>(Arrays.asList(TEST_UID)));
+        assertThat(gwpc.containsUid(TEST_UID)).isTrue();
+
+        gwpc.onRunningAppsChanged(new ArraySet<>());
+        assertThat(gwpc.containsUid(TEST_UID)).isFalse();
+    }
+
+    @Test
+    public void isEnteringPipAllowed_falseByDefault() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isFalse();
+        verify(mPipBlockedCallback).onEnteringPipBlocked(TEST_UID);
+    }
+
+    @Test
+    public void isEnteringPipAllowed_dpcSupportsPinned_allowed() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setSupportedWindowingModes(new HashSet<>(
+                Arrays.asList(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+                        WindowConfiguration.WINDOWING_MODE_PINNED)));
+        assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isTrue();
+        verify(mPipBlockedCallback, never()).onEnteringPipBlocked(TEST_UID);
+    }
+
+    @Test
+    public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void activityDoesNotSupportDisplayOnRemoteDevices_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ false,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void openBlockedComponentOnVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent(BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void addActivityPolicyExemption_openBlockedOnVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(true);
+        gwpc.addActivityPolicyExemption(BLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void openNotAllowedComponentOnBlocklistVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void addActivityPolicyExemption_openNotAllowedOnVirtualDisplay_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(false);
+        gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void openAllowedComponentOnBlocklistVirtualDisplay_startsActivity() {
+        GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void addActivityPolicyExemption_openAllowedOnVirtualDisplay_startsActivity() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(false);
+        gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_mismatchingUserHandle_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null,
+                /* uid */ UserHandle.PER_USER_RANGE + 1);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponent_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentExplicitlyBlocked_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent(
+                BLOCKED_APP_STREAMING_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentExemptFromStreaming_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(true);
+        gwpc.addActivityPolicyExemption(BLOCKED_APP_STREAMING_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentNotAllowlisted_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_blockedAppStreamingComponentNotExemptFromBlocklist_isNeverBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+        gwpc.setActivityLaunchDefaultAllowed(false);
+        gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_APP_STREAMING_COMPONENT.getPackageName(),
+                BLOCKED_APP_STREAMING_COMPONENT.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_customDisplayCategoryMatches_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ DISPLAY_CATEGORY);
+
+        assertActivityCanBeLaunched(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_customDisplayCategoryDoesNotMatch_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ "some.random.category");
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunch_fromDefaultDisplay_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, Display.DEFAULT_DISPLAY, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notExplicitlyBlocked_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor(
+                BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertActivityCanBeLaunched(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_explicitlyBlocked_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor(
+                BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notAllowed_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed(
+                NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_allowed_isNotBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed(
+                NONBLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityCanBeLaunched(gwpc, DISPLAY_ID, true,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_unsupportedWindowingMode_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, true, WindowConfiguration.WINDOWING_MODE_PINNED,
+                activityInfo);
+    }
+
+    @Test
+    public void canActivityBeLaunched_permissionComponent_isBlocked() {
+        GenericWindowPolicyController gwpc = createGwpcWithPermissionComponent(BLOCKED_COMPONENT);
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                BLOCKED_PACKAGE_NAME,
+                BLOCKED_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertActivityIsBlocked(gwpc, activityInfo);
+    }
+
+    @Test
+    public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.registerRunningAppsChangedListener(mRunningAppsChangedListener);
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1);
+        verify(mRunningAppsChangedListener).onRunningAppsChanged(uids);
+    }
+
+    @Test
+    public void onRunningAppsChanged_empty_onDisplayEmpty() {
+        ArraySet<Integer> uids = new ArraySet<>();
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+        verify(mActivityListener).onDisplayEmpty(DISPLAY_ID);
+    }
+
+    @Test
+    public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+        verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids);
+    }
+
+    @Test
+    public void registerUnregisterRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.registerRunningAppsChangedListener(mRunningAppsChangedListener);
+        gwpc.unregisterRunningAppsChangedListener(mRunningAppsChangedListener);
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+        verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids);
+    }
+
+    @Test
+    public void canActivityBeLaunched_intentInterceptedWhenRegistered_activityNoLaunch()
+            throws RemoteException {
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(TEST_SITE));
+
+        IVirtualDeviceIntentInterceptor.Stub interceptor =
+                mock(IVirtualDeviceIntentInterceptor.Stub.class);
+        doNothing().when(interceptor).onIntentIntercepted(any());
+        doReturn(interceptor).when(interceptor).asBinder();
+        doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
+
+        GenericWindowPolicyController gwpc = createGwpc();
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        // register interceptor and intercept intent
+        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(true);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+                .isFalse();
+
+        // unregister interceptor and launch activity
+        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+                .isTrue();
+    }
+
+    @Test
+    public void canActivityBeLaunched_noMatchIntentFilter_activityLaunches() {
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("testing"));
+
+        GenericWindowPolicyController gwpc = createGwpc();
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        // register interceptor with different filter
+        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+                .isTrue();
+        verify(mIntentListenerCallback).shouldInterceptIntent(any(Intent.class));
+    }
+
+    @Test
+    public void onTopActivitychanged_null_noCallback() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        gwpc.onTopActivityChanged(null, 0, 0);
+        verify(mActivityListener, never())
+                .onTopActivityChanged(anyInt(), any(ComponentName.class), anyInt());
+    }
+
+    @Test
+    public void onTopActivitychanged_activityListenerCallbackObserved() {
+        int userId = 1000;
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        gwpc.onTopActivityChanged(BLOCKED_COMPONENT, 0, userId);
+        verify(mActivityListener)
+                .onTopActivityChanged(eq(DISPLAY_ID), eq(BLOCKED_COMPONENT), eq(userId));
+    }
+
+    @Test
+    public void keepActivityOnWindowFlagsChanged_noChange() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
+
+        verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID,
+                activityInfo.applicationInfo.uid);
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+    }
+
+    @Test
+    public void keepActivityOnWindowFlagsChanged_flagSecure_isAllowedAfterTM() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, FLAG_SECURE, 0)).isTrue();
+
+        verify(mSecureWindowCallback).onSecureWindowShown(DISPLAY_ID,
+                activityInfo.applicationInfo.uid);
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+    }
+
+    @Test
+    public void keepActivityOnWindowFlagsChanged_systemFlagHideNonSystemOverlayWindows_isAllowedAfterTM() {
+        GenericWindowPolicyController gwpc = createGwpc();
+        gwpc.setDisplayId(DISPLAY_ID);
+
+        ActivityInfo activityInfo = getActivityInfo(
+                NONBLOCKED_APP_PACKAGE_NAME,
+                NONBLOCKED_APP_PACKAGE_NAME,
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0,
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)).isTrue();
+
+        verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID,
+                activityInfo.applicationInfo.uid);
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+    }
+
+    @Test
+    public void getCustomHomeComponent_noneSet() {
+        GenericWindowPolicyController gwpc = createGwpc();
+
+        assertThat(gwpc.getCustomHomeComponent()).isNull();
+    }
+
+    @Test
+    public void getCustomHomeComponent_returnsHomeComponent() {
+        GenericWindowPolicyController gwpc = createGwpcWithCustomHomeComponent(
+                NONBLOCKED_COMPONENT);
+
+        assertThat(gwpc.getCustomHomeComponent()).isEqualTo(NONBLOCKED_COMPONENT);
+    }
+
+    private GenericWindowPolicyController createGwpc() {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ mSecureWindowCallback,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithCustomHomeComponent(
+            ComponentName homeComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ homeComponent);
+    }
+
+    private GenericWindowPolicyController createGwpcWithBlockedComponent(
+            ComponentName blockedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ Collections.singleton(blockedComponent),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithAllowedComponent(
+            ComponentName allowedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ false,
+                /* activityPolicyExemptions= */ Collections.singleton(allowedComponent),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithDisplayCategory(
+            String displayCategory) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ Collections.singleton(displayCategory),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithCrossTaskNavigationBlockedFor(
+            ComponentName blockedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ true,
+                /* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithCrossTaskNavigationAllowed(
+            ComponentName allowedComponent) {
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ false,
+                /* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent),
+                /* permissionDialogComponent= */ null,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private GenericWindowPolicyController createGwpcWithPermissionComponent(
+            ComponentName permissionComponent) {
+        //TODO instert the component
+        return new GenericWindowPolicyController(
+                0,
+                0,
+                /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
+                /* activityLaunchAllowedByDefault= */ true,
+                /* activityPolicyExemptions= */ new ArraySet<>(),
+                /* crossTaskNavigationAllowedByDefault= */ false,
+                /* crossTaskNavigationExemptions= */ new ArraySet<>(),
+                /* permissionDialogComponent= */ permissionComponent,
+                /* activityListener= */ mActivityListener,
+                /* pipBlockedCallback= */ mPipBlockedCallback,
+                /* activityBlockedCallback= */ mActivityBlockedCallback,
+                /* secureWindowCallback= */ null,
+                /* intentListenerCallback= */ mIntentListenerCallback,
+                /* displayCategories= */ new ArraySet<>(),
+                /* showTasksInHostDeviceRecents= */ true,
+                /* customHomeComponent= */ null);
+    }
+
+    private Set<UserHandle> getCurrentUserId() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        return new ArraySet<>(Arrays.asList(context.getUser()));
+    }
+
+    private ActivityInfo getActivityInfo(
+            String packageName, String name, boolean displayOnRemoteDevices,
+            String requiredDisplayCategory) {
+        return getActivityInfo(packageName, name, displayOnRemoteDevices, requiredDisplayCategory,
+                0);
+    }
+
+    private ActivityInfo getActivityInfo(
+            String packageName, String name, boolean displayOnRemoteDevices,
+            String requiredDisplayCategory, int uid) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = packageName;
+        activityInfo.name = name;
+        activityInfo.flags = displayOnRemoteDevices
+                ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
+        activityInfo.applicationInfo = applicationInfo;
+        activityInfo.requiredDisplayCategory = requiredDisplayCategory;
+        return activityInfo;
+    }
+
+    private void assertActivityCanBeLaunched(GenericWindowPolicyController gwpc,
+            ActivityInfo activityInfo) {
+        assertActivityCanBeLaunched(gwpc, DISPLAY_ID, false,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    private void assertActivityCanBeLaunched(GenericWindowPolicyController gwpc, int fromDisplay,
+            boolean isNewTask, int windowingMode, ActivityInfo activityInfo) {
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
+                isNewTask)).isTrue();
+
+        verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo);
+        verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+    }
+
+    private void assertActivityIsBlocked(GenericWindowPolicyController gwpc,
+            ActivityInfo activityInfo) {
+        assertActivityIsBlocked(gwpc, DISPLAY_ID, false,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo);
+    }
+
+    private void assertActivityIsBlocked(GenericWindowPolicyController gwpc, int fromDisplay,
+            boolean isNewTask, int windowingMode, ActivityInfo activityInfo) {
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
+                isNewTask)).isFalse();
+
+        verify(mActivityBlockedCallback).onActivityBlocked(fromDisplay, activityInfo);
+        verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 1e6306cb4071454c156aed11db36b1a9a5dea65f..263d4701eba144e12dc7f9361efef03bbca0bdcd 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -24,6 +24,7 @@ import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.content.Context.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -70,6 +71,7 @@ import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.hardware.Sensor;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
@@ -303,7 +305,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices= */ true,
+                /* displayOnRemoteDevices= */ true,
                 targetDisplayCategory);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -314,12 +316,12 @@ public class VirtualDeviceManagerServiceTest {
 
 
     private ActivityInfo getActivityInfo(
-            String packageName, String name, boolean displayOnRemoveDevices,
+            String packageName, String name, boolean displayOnRemoteDevices,
             String requiredDisplayCategory) {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.packageName = packageName;
         activityInfo.name = name;
-        activityInfo.flags = displayOnRemoveDevices
+        activityInfo.flags = displayOnRemoteDevices
                 ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
         activityInfo.applicationInfo = mApplicationInfoMock;
         activityInfo.requiredDisplayCategory = requiredDisplayCategory;
@@ -1427,7 +1429,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1448,7 +1450,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
                 PERMISSION_CONTROLLER_PACKAGE_NAME,
-                /* displayOnRemoveDevices */  false,
+                /* displayOnRemoteDevices */  false,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1513,7 +1515,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 SETTINGS_PACKAGE_NAME,
                 SETTINGS_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1534,7 +1536,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 VENDING_PACKAGE_NAME,
                 VENDING_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1555,7 +1557,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 GOOGLE_DIALER_PACKAGE_NAME,
                 GOOGLE_DIALER_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1576,7 +1578,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 GOOGLE_MAPS_PACKAGE_NAME,
                 GOOGLE_MAPS_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
                 activityInfo, mAssociationInfo.getDisplayName());
@@ -1615,6 +1617,54 @@ public class VirtualDeviceManagerServiceTest {
         assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
     }
 
+    @Test
+    public void canActivityBeLaunched_permissionDialog_flagDisabled_isBlocked() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+        VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
+        mDeviceImpl.close();
+        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+                DISPLAY_ID_1);
+        ComponentName permissionComponent = getPermissionDialogComponent();
+        ActivityInfo activityInfo = getActivityInfo(
+                permissionComponent.getPackageName(),
+                permissionComponent.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isFalse();
+
+        Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+                activityInfo, mAssociationInfo.getDisplayName());
+        verify(mContext).startActivityAsUser(argThat(intent ->
+                intent.filterEquals(blockedAppIntent)), any(), any());
+    }
+
+    @Test
+    public void canActivityBeLaunched_permissionDialog_flagEnabled_isStreamed() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+        VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
+        mDeviceImpl.close();
+        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
+
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
+                DISPLAY_ID_1);
+        ComponentName permissionComponent = getPermissionDialogComponent();
+        ActivityInfo activityInfo = getActivityInfo(
+                permissionComponent.getPackageName(),
+                permissionComponent.getClassName(),
+                /* displayOnRemoteDevices */ true,
+                /* targetDisplayCategory */ null);
+        assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isTrue();
+    }
+
     @Test
     public void canActivityBeLaunched_activityCanLaunch() {
         Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
@@ -1624,7 +1674,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
         assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
@@ -1648,7 +1698,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
 
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
@@ -1691,7 +1741,7 @@ public class VirtualDeviceManagerServiceTest {
         ActivityInfo activityInfo = getActivityInfo(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoveDevices */ true,
+                /* displayOnRemoteDevices */ true,
                 /* targetDisplayCategory */ null);
 
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
@@ -1812,6 +1862,13 @@ public class VirtualDeviceManagerServiceTest {
                 NONBLOCKED_APP_PACKAGE_NAME);
     }
 
+    private ComponentName getPermissionDialogComponent() {
+        Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
+        PackageManager packageManager = mContext.getPackageManager();
+        intent.setPackage(packageManager.getPermissionControllerPackageName());
+        return intent.resolveActivity(packageManager);
+    }
+
     /** Helper class to drop permissions temporarily and restore them at the end of a test. */
     static final class DropShellPermissionsTemporarily implements AutoCloseable {
         DropShellPermissionsTemporarily() {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index 90d9452fb9c32789e0afb694de9fee691a981cf5..07dd59d2e2d807b402905464a4d4826673c11f1a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -32,11 +32,12 @@ import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.flags.Flags;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -51,6 +52,9 @@ public class VirtualDeviceTest {
     private static final String DEVICE_NAME = "VirtualDeviceName";
     private static final String DISPLAY_NAME = "DisplayName";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private IVirtualDevice mVirtualDevice;
 
@@ -101,9 +105,10 @@ public class VirtualDeviceTest {
         assertThat(device.getDisplayName().toString()).isEqualTo(DISPLAY_NAME);
     }
 
-    @RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS)
     @Test
     public void virtualDevice_getDisplayIds() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
         VirtualDevice virtualDevice =
                 new VirtualDevice(
                         mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
@@ -116,9 +121,10 @@ public class VirtualDeviceTest {
         assertThat(virtualDevice.getDisplayIds()).isEqualTo(displayIds);
     }
 
-    @RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS)
     @Test
     public void virtualDevice_hasCustomSensorSupport() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
+
         VirtualDevice virtualDevice =
                 new VirtualDevice(
                         mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index bfa279d5c3d5245a962e66335eb5b2d86201c6cd..2bf13857e537087e7e7280b07c671b3402762fd0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -212,7 +212,30 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
         mController.dispatchPendingEvents();
 
         assertTaskFragmentParentInfoChangedTransaction(mTask);
-        assertTaskFragmentAppearedTransaction();
+        assertTaskFragmentAppearedTransaction(false /* hasSurfaceControl */);
+    }
+
+    @Test
+    public void testOnTaskFragmentAppeared_systemOrganizer() {
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+
+        // No-op when the TaskFragment is not attached.
+        mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+        mController.dispatchPendingEvents();
+
+        verify(mOrganizer, never()).onTransactionReady(any());
+
+        // Send callback when the TaskFragment is attached.
+        setupMockParent(mTaskFragment, mTask);
+
+        mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+        mController.dispatchPendingEvents();
+
+        assertTaskFragmentParentInfoChangedTransaction(mTask);
+
+        // System organizer should receive the SurfaceControl
+        assertTaskFragmentAppearedTransaction(true /* hasSurfaceControl */);
     }
 
     @Test
@@ -1664,7 +1687,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
     }
 
     /** Asserts that there will be a transaction for TaskFragment appeared. */
-    private void assertTaskFragmentAppearedTransaction() {
+    private void assertTaskFragmentAppearedTransaction(boolean hasSurfaceControl) {
         verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
         final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
         final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
@@ -1675,6 +1698,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
         assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
         assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
         assertEquals(mFragmentToken, change.getTaskFragmentToken());
+        if (hasSurfaceControl) {
+            assertNotNull(change.getTaskFragmentSurfaceControl());
+        } else {
+            assertNull(change.getTaskFragmentSurfaceControl());
+        }
     }
 
     /** Asserts that there will be a transaction for TaskFragment info changed. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 6a9bb6c85c705d19935f1b3cc85d1928270160ed..5205bb0038c0cdce0eb76fd49f07542f35a0b903 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -27,8 +27,12 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -123,6 +127,17 @@ public class TaskFragmentTest extends WindowTestsBase {
         assertEquals(parentSw, mTaskFragment.getConfiguration().smallestScreenWidthDp);
     }
 
+    @Test
+    public void testUpdateOrganizedTaskFragmentSurface_noSurfaceUpdateWhenOrganizedBySystem() {
+        clearInvocations(mTransaction);
+        mTaskFragment.mIsSurfaceManagedBySystemOrganizer = true;
+
+        mTaskFragment.updateOrganizedTaskFragmentSurface();
+
+        verify(mTransaction, never()).setPosition(eq(mLeash), anyFloat(), anyFloat());
+        verify(mTransaction, never()).setWindowCrop(eq(mLeash), anyInt(), anyInt());
+    }
+
     @Test
     public void testShouldStartChangeTransition_relativePositionChange() {
         final Task task = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 36a8fc1c43a0db6b44d1137f95247ffa4658b7a7..a5e0638fec9521faeae4112ab88b0f2e498deab0 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -18,6 +18,7 @@ package android.telephony;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,6 +53,7 @@ import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
 
 import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.util.List;
@@ -9436,22 +9438,9 @@ public class CarrierConfigManager {
      * </carrier_config>
      * }</pre>
      * <p>
-     * If this carrier config is not present, the device overlay config
-     * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
-     * is present, the supported services associated with the PLMNs listed in the carrier config
-     * will override that of the device overlay config. The supported satellite services will be
-     * identified as follows:
-     * <ul>
-     * <li>For each PLMN that exists only in the carrier provided satellite services, use the
-     * carrier provided services as the supported services.</li>
-     * <li>For each PLMN that is present only in the device provided satellite services, use the
-     * device provided services as the supported services.</li>
-     * <li>For each PLMN that is present in both the carrier provided and device provided satellite
-     * services, use the carrier provided services as the supported services.</li>
-     * </ul>
-     * <p>
      * This config is empty by default.
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
             "carrier_supported_satellite_services_per_provider_bundle";
 
@@ -9462,9 +9451,8 @@ public class CarrierConfigManager {
      * satellite provider and the carrier before enabling this flag.
      *
      * The default value is false.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL =
             "satellite_attach_supported_bool";
 
@@ -10430,8 +10418,12 @@ public class CarrierConfigManager {
         sDefaults.putAll(Iwlan.getDefaults());
         sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, new String[0]);
         sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false);
-        sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
-                new int[] {4 /* BUSY */});
+        if (Flags.doNotOverridePreciseLabel()) {
+            sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, new int[]{});
+        } else {
+            sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+                    new int[]{4 /* BUSY */});
+        }
         sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
         sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 5000);
         sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index a4527f12a19955aee56d0f21003fb028e66bea3f..8dc2de8178c432b251e24d0d451b7d5b56940790 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -84,30 +84,12 @@ public final class SatelliteManager {
      */
     @Nullable private final Context mContext;
 
-    /**
-     * Create a new SatelliteManager object pinned to the given subscription ID.
-     * This is needed only to handle carrier specific satellite features.
-     * For eg: requestSatelliteAttachEnabledForCarrier and
-     *         requestIsSatelliteAttachEnabledForCarrier
-     *
-     * @return a SatelliteManager that uses the given subId for all satellite activities.
-     * @throws IllegalArgumentException if the subscription is invalid.
-     * @hide
-     */
-    public SatelliteManager createForSubscriptionId(int subId) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            throw new IllegalArgumentException("Invalid subscription ID");
-        }
-        return new SatelliteManager(mContext, subId);
-    }
-
     /**
      * Create an instance of the SatelliteManager.
      *
      * @param context The context the SatelliteManager belongs to.
      * @hide
      */
-
     public SatelliteManager(@Nullable Context context) {
         this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
     }
@@ -846,8 +828,8 @@ public final class SatelliteManager {
     /**
      * Satellite communication restricted by geolocation. This can be
      * triggered based upon geofence input provided by carrier to enable or disable satellite.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
 
     /** @hide */
@@ -1643,26 +1625,28 @@ public final class SatelliteManager {
      * {@code true}.</li>
      * </ul>
      *
+     * @param subId The subscription ID of the carrier.
      * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteAttachEnabledForCarrier(boolean enableSatellite,
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void requestSatelliteAttachEnabledForCarrier(int subId, boolean enableSatellite,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
 
         if (enableSatellite) {
-            removeSatelliteAttachRestrictionForCarrier(
+            removeSatelliteAttachRestrictionForCarrier(subId,
                     SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
         } else {
-            addSatelliteAttachRestrictionForCarrier(
+            addSatelliteAttachRestrictionForCarrier(subId,
                     SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
         }
     }
@@ -1671,6 +1655,7 @@ public final class SatelliteManager {
      * Request to get whether the carrier supported satellite plmn scan and attach by modem is
      * enabled by user.
      *
+     * @param subId The subscription ID of the carrier.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
@@ -1681,16 +1666,17 @@ public final class SatelliteManager {
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestIsSatelliteAttachEnabledForCarrier(
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void requestIsSatelliteAttachEnabledForCarrier(int subId,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
-        Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier();
+        Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier(subId);
         executor.execute(() -> callback.onResult(
                 !restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)));
     }
@@ -1699,19 +1685,25 @@ public final class SatelliteManager {
      * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
      * by modem.
      *
+     * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void addSatelliteAttachRestrictionForCarrier(
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void addSatelliteAttachRestrictionForCarrier(int subId,
             @SatelliteCommunicationRestrictionReason int reason,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -1722,7 +1714,7 @@ public final class SatelliteManager {
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.addSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+                telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1736,19 +1728,25 @@ public final class SatelliteManager {
      * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
      * by modem.
      *
+     * @param subId The subscription ID of the carrier.
      * @param reason Reason for disallowing satellite communication.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void removeSatelliteAttachRestrictionForCarrier(
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public void removeSatelliteAttachRestrictionForCarrier(int subId,
             @SatelliteCommunicationRestrictionReason int reason,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -1759,7 +1757,7 @@ public final class SatelliteManager {
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.removeSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+                telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1773,34 +1771,40 @@ public final class SatelliteManager {
      * Get reasons for disallowing satellite attach, as requested by
      * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
      *
+     * @param subId The subscription ID of the carrier.
      * @return Set of reasons for disallowing satellite communication.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @hide
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteCommunicationRestrictionReason
-    public @NonNull Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier() {
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @NonNull public Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 int[] receivedArray =
-                        telephony.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+                        telephony.getSatelliteAttachRestrictionReasonsForCarrier(subId);
                 if (receivedArray.length == 0) {
-                    logd("received set is empty, create empty set");
+                    logd("receivedArray is empty, create empty set");
                     return new HashSet<>();
                 } else {
                     return Arrays.stream(receivedArray).boxed().collect(Collectors.toSet());
                 }
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                throw new IllegalStateException("Telephony service is null.");
             }
         } catch (RemoteException ex) {
             loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
-        return null;
+        return new HashSet<>();
     }
 
     private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 02661de34fb26d530d3c5268178f0e371cb0c143..0fcd0d622d365f1ca333d21b45d1b65dbc94faef 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -392,7 +392,11 @@ oneway interface ISatellite {
      *
      * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
      *                this information to determine the relevant carrier.
-     * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+     * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+     *                        supported by user subscription.
+     * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+     *                             PLMNs that are not supported by the carrier and make sure not to
+     *                             attach to them.
      * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -404,8 +408,8 @@ oneway interface ISatellite {
      *   SatelliteError:RADIO_NOT_AVAILABLE
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      */
-    void setSatellitePlmn(int simSlot, in List<String> plmnList,
-            in IIntegerConsumer resultCallback);
+    void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList,
+            in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback);
 
     /**
      * Enable or disable satellite in the cellular modem associated with a carrier.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 6451daf2e752bdff9631b1dcb09bf7487436e71c..a9c09c94d57c5c8362e502f787f99096ab34fb1b 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -214,12 +214,12 @@ public class SatelliteImplBase extends SatelliteService {
         }
 
         @Override
-        public void setSatellitePlmn(int simSlot, List<String> plmnList,
-                IIntegerConsumer errorCallback)
+        public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList,
+                List<String> devicePlmnList, IIntegerConsumer errorCallback)
                 throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this
-                            .setSatellitePlmn(simSlot, plmnList, errorCallback),
+                    () -> SatelliteImplBase.this.setSatellitePlmn(
+                            simSlot, carrierPlmnList, devicePlmnList, errorCallback),
                     "setSatellitePlmn");
         }
 
@@ -655,13 +655,15 @@ public class SatelliteImplBase extends SatelliteService {
      * SIM profile. Acquisition of satellite based system is lower priority to terrestrial
      * networks. UE shall make all attempts to acquire terrestrial service prior to camping on
      * satellite LTE service.
-     * This method must only take effect if {@link #setSatelliteEnabledForCarrier} is {@code true},
-     * and return an error otherwise.
      *
      * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
      * applied. The modem will use this information to determine the relevant carrier.
      * @param errorCallback The callback to receive the error code result of the operation.
-     * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+     * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+     *                        supported by user subscription.
+     * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+     *                             PLMNs that are not supported by the carrier and make sure not to
+     *                             attach to them.
      *
      * Valid error codes returned:
      *   SatelliteError:NONE
@@ -672,7 +674,8 @@ public class SatelliteImplBase extends SatelliteService {
      *   SatelliteError:RADIO_NOT_AVAILABLE
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      */
-    public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> plmnList,
+    public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
+            @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 95cbff9eeb1235a9a0b94b0324524c69eacec24a..d176b5e97b0b96b94339215cf6ab1dc9d209a1ed 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -69,7 +69,8 @@ public class HostTestUtils {
             String methodDescriptor,
             String callbackMethod
     ) {
-        callStaticMethodByName(callbackMethod, methodClass, methodName, methodDescriptor);
+        callStaticMethodByName(callbackMethod, "method call hook", methodClass,
+                methodName, methodDescriptor);
     }
 
     /**
@@ -167,31 +168,38 @@ public class HostTestUtils {
         logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName()
                 + " calling hook " + callbackMethod);
 
-        callStaticMethodByName(callbackMethod, loadedClass);
+        callStaticMethodByName(callbackMethod, "class load hook", loadedClass);
     }
 
-    private static void callStaticMethodByName(String classAndMethodName, Object... args) {
+    private static void callStaticMethodByName(String classAndMethodName,
+            String description, Object... args) {
         // Forward the call to callbackMethod.
         final int lastPeriod = classAndMethodName.lastIndexOf(".");
-        final String className = classAndMethodName.substring(0, lastPeriod);
-        final String methodName = classAndMethodName.substring(lastPeriod + 1);
 
-        if (lastPeriod < 0 || className.isEmpty() || methodName.isEmpty()) {
+        if ((lastPeriod) < 0 || (lastPeriod == classAndMethodName.length() - 1)) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: malformed method name \"%s\"",
+                    "Unable to find %s: malformed method name \"%s\"",
+                    description,
                     classAndMethodName));
         }
 
+        final String className = classAndMethodName.substring(0, lastPeriod);
+        final String methodName = classAndMethodName.substring(lastPeriod + 1);
+
         Class<?> clazz = null;
         try {
             clazz = Class.forName(className);
         } catch (Exception e) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: Class %s not found", className), e);
+                    "Unable to find %s: Class %s not found",
+                    description,
+                    className), e);
         }
         if (!Modifier.isPublic(clazz.getModifiers())) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: Class %s must be public", className));
+                    "Unable to find %s: Class %s must be public",
+                    description,
+                    className));
         }
 
         Class<?>[] argTypes = new Class[args.length];
@@ -204,25 +212,23 @@ public class HostTestUtils {
             method = clazz.getMethod(methodName, argTypes);
         } catch (Exception e) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: class %s doesn't have method %s"
+                    "Unable to find %s: class %s doesn't have method %s"
                             + " (method must take exactly one parameter of type Class,"
                             + " and public static)",
-                    className,
-                    methodName), e);
+                    description, className, methodName), e);
         }
         if (!(Modifier.isPublic(method.getModifiers())
                 && Modifier.isStatic(method.getModifiers()))) {
             throw new HostTestException(String.format(
-                    "Unable to find class load hook: Method %s in class %s must be public static",
-                    methodName, className));
+                    "Unable to find %s: Method %s in class %s must be public static",
+                    description, methodName, className));
         }
         try {
             method.invoke(null, args);
         } catch (Exception e) {
             throw new HostTestException(String.format(
-                    "Unable to invoke class load hook %s.%s",
-                    className,
-                    methodName), e);
+                    "Unable to invoke %s %s.%s",
+                    description, className, methodName), e);
         }
     }
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 7d7852ab162e9d760b34b410fc113a448839b23b..f75062b3a878d82d14b1ca827fb416b6da5bc5d4 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -32,6 +32,10 @@ fun normalizeTextLine(s: String): String {
     return uncommented.trim()
 }
 
+/**
+ * Concatenate list [a] and [b] and return it. As an optimization, it returns an input
+ * [List] as-is if the other [List] is empty, so do not modify input [List]'s.
+ */
 fun <T> addLists(a: List<T>, b: List<T>): List<T> {
     if (a.isEmpty()) {
         return b
@@ -42,6 +46,10 @@ fun <T> addLists(a: List<T>, b: List<T>): List<T> {
     return a + b
 }
 
+/**
+ * Add element [b] to list [a] if [b] is not null. Otherwise, just return [a].
+ * (because the method may return [a] as-is, do not modify it after passing it.)
+ */
 fun <T> addNonNullElement(a: List<T>, b: T?): List<T> {
     if (b == null) {
         return a
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
index cbbf91b49dedaade19a8426a20f2690150ce176f..758de4dfccf820d12a98c572063d0adae55a918f 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -131,7 +131,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
                 priority = 6,
                 severity = Severity.ERROR,
                 implementation = Implementation(
-                        EnforcePermissionDetector::class.java,
+                        EnforcePermissionHelperDetector::class.java,
                         EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
                 )
         )