Merge "[pm/metrics] add loadingCompletedTime in stats" into udc-dev
diff --git a/Android.bp b/Android.bp
index bc6cda3..cff863b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -343,6 +343,7 @@
             "hardware/interfaces/biometrics/fingerprint/aidl",
             "hardware/interfaces/graphics/common/aidl",
             "hardware/interfaces/keymaster/aidl",
+            "system/hardware/interfaces/media/aidl",
         ],
     },
     dxflags: [
@@ -632,6 +633,7 @@
             "hardware/interfaces/biometrics/fingerprint/aidl",
             "hardware/interfaces/graphics/common/aidl",
             "hardware/interfaces/keymaster/aidl",
+            "system/hardware/interfaces/media/aidl",
         ],
     },
     // These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 90b6603..a46ecce 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -422,27 +422,26 @@
         "$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
 }
 
-// Disable doc generation until Doclava is migrated to JDK 17 (b/240421555).
-// java_genrule {
-//     name: "ds-docs-switched",
-//     tools: [
-//         "switcher4",
-//         "soong_zip",
-//     ],
-//     srcs: [
-//         ":ds-docs-java{.docs.zip}",
-//         ":ds-docs-kt{.docs.zip}",
-//     ],
-//     out: ["ds-docs-switched.zip"],
-//     dist: {
-//         targets: ["docs"],
-//     },
-//     cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
-//         "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
-//         "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
-//         "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
-//         "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
-// }
+java_genrule {
+    name: "ds-docs-switched",
+    tools: [
+        "switcher4",
+        "soong_zip",
+    ],
+    srcs: [
+        ":ds-docs-java{.docs.zip}",
+        ":ds-docs-kt{.docs.zip}",
+    ],
+    out: ["ds-docs-switched.zip"],
+    dist: {
+        targets: ["docs"],
+    },
+    cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
+        "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
+        "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
+        "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
+        "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+}
 
 droiddoc {
     name: "ds-static-docs",
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 1d93eb3..e38e21f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2625,7 +2625,7 @@
 
                 final Bundle mostRecentDeliveryOptions = BroadcastOptions.makeBasic()
                         .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-                        .setDeferUntilActive(true)
+                        .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                         .toBundle();
 
                 mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 1151bb7..394be6e 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1948,7 +1948,7 @@
                             | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             mTimeTickOptions = BroadcastOptions.makeBasic()
                     .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-                    .setDeferUntilActive(true)
+                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                     .toBundle();
             mTimeTickTrigger = new IAlarmListener.Stub() {
                 @Override
diff --git a/core/api/current.txt b/core/api/current.txt
index 443954b..02de1cd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -224,6 +224,7 @@
     field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
     field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
     field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+    field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
     field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
     field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA";
     field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
@@ -9678,6 +9679,7 @@
     method @Nullable public String getAttributionTag();
     method @Nullable public android.content.AttributionSource getNext();
     method @Nullable public String getPackageName();
+    method public int getPid();
     method public int getUid();
     method public boolean isTrusted(@NonNull android.content.Context);
     method @NonNull public static android.content.AttributionSource myAttributionSource();
@@ -9692,6 +9694,7 @@
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
+    method @NonNull public android.content.AttributionSource.Builder setPid(int);
   }
 
   public abstract class BroadcastReceiver {
@@ -11401,7 +11404,7 @@
   public class RestrictionsManager {
     method public static android.os.Bundle convertRestrictionsToBundle(java.util.List<android.content.RestrictionEntry>);
     method public android.content.Intent createLocalApprovalIntent();
-    method @Deprecated public android.os.Bundle getApplicationRestrictions();
+    method public android.os.Bundle getApplicationRestrictions();
     method @NonNull @WorkerThread public java.util.List<android.os.Bundle> getApplicationRestrictionsPerAdmin();
     method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(String);
     method public boolean hasRestrictionsProvider();
@@ -27343,7 +27346,9 @@
     field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
     field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
     field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+    field public static final String TV_MESSAGE_KEY_RAW_DATA = "android.media.tv.TvInputManager.raw_data";
     field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
+    field public static final String TV_MESSAGE_KEY_SUBTYPE = "android.media.tv.TvInputManager.subtype";
     field public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; // 0x2
     field public static final int TV_MESSAGE_TYPE_OTHER = 1000; // 0x3e8
     field public static final int TV_MESSAGE_TYPE_WATERMARK = 1; // 0x1
@@ -33610,6 +33615,7 @@
     method @Deprecated public static final boolean supportsProcesses();
     field public static final int BLUETOOTH_UID = 1002; // 0x3ea
     field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
+    field public static final int INVALID_PID = -1; // 0xffffffff
     field public static final int INVALID_UID = -1; // 0xffffffff
     field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
     field public static final int PHONE_UID = 1001; // 0x3e9
@@ -33861,7 +33867,7 @@
 
   public class UserManager {
     method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle);
-    method @Deprecated @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
+    method @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
     method public long getSerialNumberForUser(android.os.UserHandle);
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount();
     method public long getUserCreationTime(android.os.UserHandle);
@@ -40588,7 +40594,7 @@
     method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
     method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
     method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
-    method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
+    method @NonNull @RequiresPermission(android.Manifest.permission.PROVIDE_REMOTE_CREDENTIALS) public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
   }
 
   public class BeginGetCredentialOption implements android.os.Parcelable {
@@ -40637,7 +40643,7 @@
     method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
     method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setAuthenticationActions(@NonNull java.util.List<android.service.credentials.Action>);
     method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
-    method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
+    method @NonNull @RequiresPermission(android.Manifest.permission.PROVIDE_REMOTE_CREDENTIALS) public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
   }
 
   public final class CallingAppInfo implements android.os.Parcelable {
@@ -41555,7 +41561,6 @@
 
   public abstract class RecognitionService extends android.app.Service {
     ctor public RecognitionService();
-    method public void clearModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
     method public int getMaxConcurrentSessionsCount();
     method public final android.os.IBinder onBind(android.content.Intent);
     method protected abstract void onCancel(android.speech.RecognitionService.Callback);
@@ -41565,7 +41570,7 @@
     method protected abstract void onStopListening(android.speech.RecognitionService.Callback);
     method public void onTriggerModelDownload(@NonNull android.content.Intent);
     method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
-    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
+    method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
     field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
     field public static final String SERVICE_META_DATA = "android.speech";
   }
@@ -41690,18 +41695,17 @@
   public class SpeechRecognizer {
     method @MainThread public void cancel();
     method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.RecognitionSupportCallback);
-    method public void clearModelDownloadListener(@NonNull android.content.Intent);
     method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context);
     method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context);
     method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName);
     method public void destroy();
     method public static boolean isOnDeviceRecognitionAvailable(@NonNull android.content.Context);
     method public static boolean isRecognitionAvailable(@NonNull android.content.Context);
-    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
     method @MainThread public void setRecognitionListener(android.speech.RecognitionListener);
     method @MainThread public void startListening(android.content.Intent);
     method @MainThread public void stopListening();
     method public void triggerModelDownload(@NonNull android.content.Intent);
+    method public void triggerModelDownload(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
     field public static final String CONFIDENCE_SCORES = "confidence_scores";
     field public static final String DETECTED_LANGUAGE = "detected_language";
     field public static final int ERROR_AUDIO = 3; // 0x3
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d573e33..fb7ce5b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -255,7 +255,6 @@
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
     field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE";
-    field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String PROVISION_DEMO_DEVICE = "android.permission.PROVISION_DEMO_DEVICE";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 574ed6f..445b957 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2027,7 +2027,9 @@
     method public static boolean is64BitAbi(String);
     method public static boolean isDebuggable();
     field @Nullable public static final String BRAND_FOR_ATTESTATION;
+    field @Nullable public static final String DEVICE_FOR_ATTESTATION;
     field public static final boolean IS_EMULATOR;
+    field @Nullable public static final String MANUFACTURER_FOR_ATTESTATION;
     field @Nullable public static final String MODEL_FOR_ATTESTATION;
     field @Nullable public static final String PRODUCT_FOR_ATTESTATION;
   }
@@ -3986,6 +3988,7 @@
 
   public static class WindowInfosListenerForTest.WindowInfo {
     field @NonNull public final android.graphics.Rect bounds;
+    field public final boolean isTrustedOverlay;
     field @NonNull public final String name;
     field @NonNull public final android.os.IBinder windowToken;
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3ada76..2751b54 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -849,8 +849,10 @@
         @UnsupportedAppUsage
         Intent intent;
         boolean rebind;
+        long bindSeq;
         public String toString() {
-            return "BindServiceData{token=" + token + " intent=" + intent + "}";
+            return "BindServiceData{token=" + token + " intent=" + intent
+                    + " bindSeq=" + bindSeq + "}";
         }
     }
 
@@ -1107,12 +1109,13 @@
         }
 
         public final void scheduleBindService(IBinder token, Intent intent,
-                boolean rebind, int processState) {
+                boolean rebind, int processState, long bindSeq) {
             updateProcessState(processState, false);
             BindServiceData s = new BindServiceData();
             s.token = token;
             s.intent = intent;
             s.rebind = rebind;
+            s.bindSeq = bindSeq;
 
             if (DEBUG_SERVICE)
                 Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
@@ -1124,6 +1127,7 @@
             BindServiceData s = new BindServiceData();
             s.token = token;
             s.intent = intent;
+            s.bindSeq = -1;
 
             sendMessage(H.UNBIND_SERVICE, s);
         }
@@ -3557,8 +3561,13 @@
             if (mLastProcessState == processState) {
                 return;
             }
+            // Do not issue a transitional GC if we are transitioning between 2 cached states.
+            // Only update if the state flips between cached and uncached or vice versa
+            if (ActivityManager.isProcStateCached(mLastProcessState)
+                    != ActivityManager.isProcStateCached(processState)) {
+                updateVmProcessState(processState);
+            }
             mLastProcessState = processState;
-            updateVmProcessState(processState);
             if (localLOGV) {
                 Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
                         + (fromIpc ? " (from ipc" : ""));
@@ -3567,12 +3576,16 @@
     }
 
     /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
+    // Currently ART VM only uses state updates for Transitional GC, and thus
+    // this function initiates a Transitional GC for transitions into Cached apps states.
     private void updateVmProcessState(int processState) {
-        // TODO: Tune this since things like gmail sync are important background but not jank
-        // perceptible.
-        final int state = processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
-                ? VM_PROCESS_STATE_JANK_PERCEPTIBLE
-                : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
+        // Only a transition into Cached state should result in a Transitional GC request
+        // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case.
+        // Note that there are 4 possible cached states currently, all of which are
+        // JANK_IMPERCEPTIBLE from GC point of view.
+        final int state = ActivityManager.isProcStateCached(processState)
+                ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE
+                : VM_PROCESS_STATE_JANK_PERCEPTIBLE;
         VMRuntime.getRuntime().updateProcessState(state);
     }
 
@@ -6644,12 +6657,13 @@
             // Setup a location to store generated/compiled graphics code.
             final Context deviceContext = context.createDeviceProtectedStorageContext();
             final File codeCacheDir = deviceContext.getCodeCacheDir();
-            if (codeCacheDir != null) {
+            final File deviceCacheDir = deviceContext.getCacheDir();
+            if (codeCacheDir != null && deviceCacheDir != null) {
                 try {
                     int uid = Process.myUid();
                     String[] packages = getPackageManager().getPackagesForUid(uid);
                     if (packages != null) {
-                        HardwareRenderer.setupDiskCache(codeCacheDir);
+                        HardwareRenderer.setupDiskCache(deviceCacheDir);
                         RenderScriptCacheDir.setupDiskCache(codeCacheDir);
                     }
                 } catch (RemoteException e) {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 181bd35..265b564 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8408,9 +8408,9 @@
     public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
-                        mContext.getAttributionSource().getToken())), message,
-                        /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+                        proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+                        message, /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8495,8 +8495,9 @@
             int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
                 mContext.getAttributionSource(), new AttributionSource(proxiedUid,
-                        proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
-                        .getToken())), message,/*skipProxyOperation*/ false);
+                        Process.INVALID_PID, proxiedPackageName, proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), message,
+                        /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8906,9 +8907,9 @@
     public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
-                        mContext.getAttributionSource().getToken())), message,
-                        /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+                        proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+                        message, /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8954,7 +8955,7 @@
             @Nullable String message) {
         return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
                 mContext.getAttributionSource(), new AttributionSource(proxiedUid,
-                        proxiedPackageName, proxiedAttributionTag,
+                        Process.INVALID_PID, proxiedPackageName, proxiedAttributionTag,
                         mContext.getAttributionSource().getToken())), message,
                         /*skipProxyOperation*/ false);
     }
@@ -9103,8 +9104,8 @@
             @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
         IBinder token = mContext.getAttributionSource().getToken();
         finishProxyOp(token, op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName,  proxiedAttributionTag,
-                        token)), /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+                        proxiedAttributionTag, token)), /*skipProxyOperation*/ false);
     }
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 762fb04..0cd42a3 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3461,7 +3461,9 @@
             @Nullable AttributionSource nextAttributionSource,
             @Nullable Set<String> renouncedPermissions) {
         AttributionSource attributionSource = new AttributionSource(Process.myUid(),
-                mOpPackageName, attributionTag, renouncedPermissions, nextAttributionSource);
+                Process.myPid(), mOpPackageName, attributionTag,
+                (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
+                nextAttributionSource);
         // If we want to access protected data on behalf of another app we need to
         // tell the OS that we opt in to participate in the attribution chain.
         if (nextAttributionSource != null) {
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 4f77203..6b5f6b0 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -95,7 +95,7 @@
     void processInBackground();
     @UnsupportedAppUsage
     void scheduleBindService(IBinder token,
-            in Intent intent, boolean rebind, int processState);
+            in Intent intent, boolean rebind, int processState, long bindSeq);
     @UnsupportedAppUsage
     void scheduleUnbindService(IBinder token,
             in Intent intent);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 0ef8e92..09450f5 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -155,6 +155,14 @@
             "android.app.extra.REMOTE_LOCKSCREEN_VALIDATION_SESSION";
 
     /**
+     * A boolean indicating that credential confirmation activity should be a task overlay.
+     * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER}.
+     * @hide
+     */
+    public static final String EXTRA_FORCE_TASK_OVERLAY =
+            "android.app.KeyguardManager.FORCE_TASK_OVERLAY";
+
+    /**
      * Result code returned by the activity started by
      * {@link #createConfirmFactoryResetCredentialIntent} or
      * {@link #createConfirmDeviceCredentialForRemoteValidationIntent}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 440ee20..e78fb17 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8657,13 +8657,13 @@
              * where the platform doesn't support the MIME type, the original text provided in the
              * constructor will be used.
              * @param dataMimeType The MIME type of the content. See
-             * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
-             * types on Android and Android Wear.
+             * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of
+             * supported image MIME types.
              * @param dataUri The uri containing the content whose type is given by the MIME type.
              * <p class="note">
+             * Notification Listeners including the System UI need permission to access the
+             * data the Uri points to. The recommended ways to do this are:
              * <ol>
-             *   <li>Notification Listeners including the System UI need permission to access the
-             *       data the Uri points to. The recommended ways to do this are:</li>
              *   <li>Store the data in your own ContentProvider, making sure that other apps have
              *       the correct permission to access your provider. The preferred mechanism for
              *       providing access is to use per-URI permissions which are temporary and only
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 39d77c4..5b95503 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -875,15 +875,6 @@
     }
 
     /**
-     * Returns true if any visible windows belonging to apps with this window configuration should
-     * be kept on screen when the app is killed due to something like the low memory killer.
-     * @hide
-     */
-    public boolean keepVisibleDeadAppWindowOnScreen() {
-        return mWindowingMode != WINDOWING_MODE_PINNED;
-    }
-
-    /**
      * Returns true if the backdrop on the client side should match the frame of the window.
      * Returns false, if the backdrop should be fullscreen.
      * @hide
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 2b400c1f..cd45f4d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -101,22 +101,28 @@
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag) {
-        this(uid, packageName, attributionTag, sDefaultToken);
+        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken);
+    }
+
+    /** @hide */
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag) {
+        this(uid, pid, packageName, attributionTag, sDefaultToken);
     }
 
     /** @hide */
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token) {
-        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
-                /*next*/ null);
+        this(uid, Process.INVALID_PID, packageName, attributionTag, token,
+                /*renouncedPermissions*/ null, /*next*/ null);
     }
 
     /** @hide */
-    public AttributionSource(int uid, @Nullable String packageName,
-            @Nullable String attributionTag, @NonNull IBinder token,
-            @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag, @NonNull IBinder token) {
+        this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+                /*next*/ null);
     }
 
     /** @hide */
@@ -124,26 +130,33 @@
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
             @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, (renouncedPermissions != null)
-                ? renouncedPermissions.toArray(new String[0]) : null, next);
+        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
+                (renouncedPermissions != null)
+                ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
     }
 
     /** @hide */
     public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
-        this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
-                current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
+        this(current.getUid(), current.getPid(), current.getPackageName(),
+                current.getAttributionTag(), current.getToken(),
+                current.mAttributionSourceState.renouncedPermissions, next);
     }
 
-    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
-            @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+    /** @hide */
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
+            @Nullable AttributionSource next) {
+        this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
     }
 
-    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
-            @NonNull IBinder token, @Nullable String[] renouncedPermissions,
+    /** @hide */
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag, @NonNull IBinder token,
+            @Nullable String[] renouncedPermissions,
             @Nullable AttributionSource next) {
         mAttributionSourceState = new AttributionSourceState();
         mAttributionSourceState.uid = uid;
+        mAttributionSourceState.pid = pid;
         mAttributionSourceState.token = token;
         mAttributionSourceState.packageName = packageName;
         mAttributionSourceState.attributionTag = attributionTag;
@@ -162,7 +175,17 @@
 
         // Since we just unpacked this object as part of it transiting a Binder
         // call, this is the perfect time to enforce that its UID and PID can be trusted
-        enforceCallingUidAndPid();
+        enforceCallingUid();
+
+        // If this object is being constructed as part of a oneway Binder call, getCallingPid will
+        // return 0 instead of the true PID. In that case, invalidate the PID by setting it to
+        // INVALID_PID (-1).
+        final int callingPid = Binder.getCallingPid();
+        if (callingPid == 0) {
+            mAttributionSourceState.pid = Process.INVALID_PID;
+        }
+
+        enforceCallingPid();
     }
 
     /** @hide */
@@ -172,23 +195,29 @@
 
     /** @hide */
     public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
-        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
-                mAttributionSourceState.renouncedPermissions, next);
+        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+                getToken(), mAttributionSourceState.renouncedPermissions, next);
     }
 
     /** @hide */
     public AttributionSource withPackageName(@Nullable String packageName) {
-        return new AttributionSource(getUid(), packageName, getAttributionTag(),
-                mAttributionSourceState.renouncedPermissions, getNext());
+        return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
+               getToken(), mAttributionSourceState.renouncedPermissions, getNext());
     }
 
     /** @hide */
     public AttributionSource withToken(@NonNull Binder token) {
-        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
                 token, mAttributionSourceState.renouncedPermissions, getNext());
     }
 
     /** @hide */
+    public AttributionSource withPid(int pid) {
+        return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
+                getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+    }
+
+    /** @hide */
     public @NonNull AttributionSourceState asState() {
         return mAttributionSourceState;
     }
@@ -228,6 +257,7 @@
         }
         try {
             return new AttributionSource.Builder(uid)
+                .setPid(Process.myPid())
                 .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
                 .build();
         } catch (Exception ignored) {
@@ -265,18 +295,6 @@
     }
 
     /**
-     * If you are handling an IPC and you don't trust the caller you need to validate whether the
-     * attribution source is one for the calling app to prevent the caller to pass you a source from
-     * another app without including themselves in the attribution chain.
-     *
-     * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
-     */
-    private void enforceCallingUidAndPid() {
-        enforceCallingUid();
-        enforceCallingPid();
-    }
-
-    /**
      * If you are handling an IPC and you don't trust the caller you need to validate
      * whether the attribution source is one for the calling app to prevent the caller
      * to pass you a source from another app without including themselves in the
@@ -312,7 +330,10 @@
     }
 
     /**
-     * Validate that the pid being claimed for the calling app is not spoofed
+     * Validate that the pid being claimed for the calling app is not spoofed.
+     *
+     * Note that the PID may be unavailable, for example if we're in a oneway Binder call. In this
+     * case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate this.
      *
      * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
      * @hide
@@ -320,8 +341,12 @@
     @TestApi
     public void enforceCallingPid() {
         if (!checkCallingPid()) {
-            throw new SecurityException("Calling pid: " + Binder.getCallingPid()
-                    + " doesn't match source pid: " + mAttributionSourceState.pid);
+            if (Binder.getCallingPid() == 0) {
+                throw new SecurityException("Calling pid unavailable due to oneway Binder call.");
+            } else {
+                throw new SecurityException("Calling pid: " + Binder.getCallingPid()
+                        + " doesn't match source pid: " + mAttributionSourceState.pid);
+            }
         }
     }
 
@@ -332,7 +357,8 @@
      */
     private boolean checkCallingPid() {
         final int callingPid = Binder.getCallingPid();
-        if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) {
+        if (mAttributionSourceState.pid != Process.INVALID_PID
+                && callingPid != mAttributionSourceState.pid) {
             return false;
         }
         return true;
@@ -449,6 +475,13 @@
     }
 
     /**
+     * The PID that is accessing the permission protected data.
+     */
+    public int getPid() {
+        return mAttributionSourceState.pid;
+    }
+
+    /**
      * The package that is accessing the permission protected data.
      */
     public @Nullable String getPackageName() {
@@ -551,6 +584,7 @@
                 throw new IllegalArgumentException("current AttributionSource can not be null");
             }
             mAttributionSourceState.uid = current.getUid();
+            mAttributionSourceState.pid = current.getPid();
             mAttributionSourceState.packageName = current.getPackageName();
             mAttributionSourceState.attributionTag = current.getAttributionTag();
             mAttributionSourceState.token = current.getToken();
@@ -559,11 +593,25 @@
         }
 
         /**
+         * The PID of the process that is accessing the permission protected data.
+         *
+         * If not called, pid will default to {@link Process@INVALID_PID} (-1). This indicates that
+         * the PID data is missing. Supplying a PID is not required, but recommended when
+         * accessible.
+         */
+        public @NonNull Builder setPid(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mAttributionSourceState.pid = value;
+            return this;
+        }
+
+        /**
          * The package that is accessing the permission protected data.
          */
         public @NonNull Builder setPackageName(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
+            mBuilderFieldsSet |= 0x4;
             mAttributionSourceState.packageName = value;
             return this;
         }
@@ -573,7 +621,7 @@
          */
         public @NonNull Builder setAttributionTag(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
+            mBuilderFieldsSet |= 0x8;
             mAttributionSourceState.attributionTag = value;
             return this;
         }
@@ -606,7 +654,7 @@
         @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
         public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
+            mBuilderFieldsSet |= 0x10;
             mAttributionSourceState.renouncedPermissions = (value != null)
                     ? value.toArray(new String[0]) : null;
             return this;
@@ -617,7 +665,7 @@
          */
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
+            mBuilderFieldsSet |= 0x20;
             mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
                     {value.mAttributionSourceState} : mAttributionSourceState.next;
             return this;
@@ -629,15 +677,18 @@
             mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x2) == 0) {
-                mAttributionSourceState.packageName = null;
+                mAttributionSourceState.pid = Process.INVALID_PID;
             }
             if ((mBuilderFieldsSet & 0x4) == 0) {
-                mAttributionSourceState.attributionTag = null;
+                mAttributionSourceState.packageName = null;
             }
             if ((mBuilderFieldsSet & 0x8) == 0) {
-                mAttributionSourceState.renouncedPermissions = null;
+                mAttributionSourceState.attributionTag = null;
             }
             if ((mBuilderFieldsSet & 0x10) == 0) {
+                mAttributionSourceState.renouncedPermissions = null;
+            }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
                 mAttributionSourceState.next = null;
             }
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 85daf15..667ec7e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5920,9 +5920,8 @@
 
     /**
      * Optional argument to be used with {@link #ACTION_CHOOSER}.
-     * A {@link android.app.PendingIntent} to be sent when the user wants to modify the content that
-     * they're sharing. This can be used to allow the user to return to the source app to, for
-     * example, select different media.
+     * A {@link ChooserAction} to allow the user to modify what is being shared in some way. This
+     * may be integrated into the content preview on sharesheets that have a preview UI.
      */
     public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION =
             "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 8d3452e..0e3217d 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -152,7 +152,7 @@
             @NonNull String permission, int pid, int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
         return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
-                packageName, attributionTag), message, startDataDelivery);
+                pid, packageName, attributionTag), message, startDataDelivery);
     }
 
     /**
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 8115292..44a84e4 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -427,11 +427,12 @@
      * @return the application restrictions as a Bundle. Returns null if there
      * are no restrictions.
      *
-     * @deprecated Use {@link #getApplicationRestrictionsPerAdmin} instead.
-     * Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, it is
-     * possible for there to be multiple managing agents on the device with the ability to set
-     * restrictions. This API will only to return the restrictions set by device policy controllers
-     * (DPCs)
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. a Device Policy Controller (DPC) and a Supervision admin.
+     * This API will only return the restrictions set by the DPCs. To retrieve restrictions
+     * set by all managing apps, use
+     * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      *
      * @see DevicePolicyManager
      */
@@ -453,8 +454,8 @@
      * stable between multiple calls.
      *
      * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-     * it is possible for there to be multiple managing agents on the device with the ability to set
-     * restrictions, e.g. an Enterprise DPC and a Supervision admin.
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
      *
      * <p>Each {@link Bundle} consists of key-value pairs, as defined by the application,
      * where the types of values may be:
@@ -471,6 +472,7 @@
      * package. Returns an empty {@link List} if there are no saved restrictions.
      *
      * @see UserManager#KEY_RESTRICTIONS_PENDING
+     * @see DevicePolicyManager
      */
     @WorkerThread
     @UserHandleAware
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index bf34c1c..a23d7e4 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -42,7 +42,7 @@
     private final String mType;
 
     /**
-     * The flattened JSON string that will be matched with requests.
+     * Flattened semicolon separated keys of JSON values to match with requests.
      */
     @NonNull
     private final String mFlattenedRequestString;
@@ -57,7 +57,8 @@
      * Constructs a {@link CredentialDescription}.
      *
      * @param type the type of the credential returned.
-     * @param flattenedRequestString flattened JSON string that will be matched with requests.
+     * @param flattenedRequestString flattened semicolon separated keys of JSON values
+     *                              to match with requests.
      * @param credentialEntries a list of {@link CredentialEntry}s that are to be shown on the
      *                          account selector if a credential matches with this description.
      *                          Each entry contains information to be displayed within an
@@ -151,4 +152,29 @@
     public List<CredentialEntry> getCredentialEntries() {
         return mCredentialEntries;
     }
+
+    /**
+     * {@link CredentialDescription#mType} and
+     * {@link CredentialDescription#mFlattenedRequestString} are enough for hashing. Constructor
+     * enforces {@link CredentialEntry} to have the same type and
+     * {@link android.app.slice.Slice} contained by the entry can not be hashed.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mFlattenedRequestString);
+    }
+
+    /**
+     * {@link CredentialDescription#mType} and
+     * {@link CredentialDescription#mFlattenedRequestString} are enough for equality check.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof CredentialDescription)) {
+            return false;
+        }
+        CredentialDescription other = (CredentialDescription) obj;
+        return mType.equals(other.mType)
+                && mFlattenedRequestString.equals(other.mFlattenedRequestString);
+    }
 }
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 0806f1d..493a4ff 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -167,6 +167,48 @@
     }
 
     /**
+     * Gets a {@link GetPendingCredentialResponse} that can launch the credential retrieval UI flow
+     * to request a user credential for your app.
+     *
+     * @param request            the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor           the callback will take place on this {@link Executor}
+     * @param callback           the callback invoked when the request succeeds or fails
+     *
+     * @hide
+     */
+    public void getPendingCredential(
+            @NonNull GetCredentialRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<
+                    GetPendingCredentialResponse, GetCredentialException> callback) {
+        requireNonNull(request, "request must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "getPendingCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote =
+                    mService.executeGetPendingCredential(
+                            request,
+                            new GetPendingCredentialTransport(executor, callback),
+                            mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
      * Launches the necessary flows to register an app credential for the user.
      *
      * <p>The execution can potentially launch UI flows to collect user consent to creating or
@@ -442,6 +484,32 @@
         }
     }
 
+    private static class GetPendingCredentialTransport extends IGetPendingCredentialCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<
+                GetPendingCredentialResponse, GetCredentialException> mCallback;
+
+        private GetPendingCredentialTransport(
+                Executor executor,
+                OutcomeReceiver<GetPendingCredentialResponse, GetCredentialException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse(GetPendingCredentialResponse response) {
+            mExecutor.execute(() -> mCallback.onResult(response));
+        }
+
+        @Override
+        public void onError(String errorType, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new GetCredentialException(errorType, message)));
+        }
+    }
+
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
diff --git a/core/java/android/credentials/GetPendingCredentialResponse.aidl b/core/java/android/credentials/GetPendingCredentialResponse.aidl
new file mode 100644
index 0000000..1cdd637
--- /dev/null
+++ b/core/java/android/credentials/GetPendingCredentialResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.credentials;
+
+parcelable GetPendingCredentialResponse;
\ No newline at end of file
diff --git a/core/java/android/credentials/GetPendingCredentialResponse.java b/core/java/android/credentials/GetPendingCredentialResponse.java
new file mode 100644
index 0000000..9005d9d
--- /dev/null
+++ b/core/java/android/credentials/GetPendingCredentialResponse.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 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 android.credentials;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * A response object that prefetches user app credentials and provides metadata about them. It can
+ * then be used to issue the full credential retrieval flow via the
+ * {@link #show(Activity, CancellationSignal, Executor, OutcomeReceiver)} method to perform the
+ * necessary flows such as consent collection and officially retrieve a credential.
+ *
+ * @hide
+ */
+public final class GetPendingCredentialResponse implements Parcelable {
+    private final boolean mHasCredentialResults;
+    private final boolean mHasAuthenticationResults;
+    private final boolean mHasRemoteResults;
+
+    /** Returns true if the user has any candidate credentials, and false otherwise. */
+    public boolean hasCredentialResults() {
+        return mHasCredentialResults;
+    }
+
+    /**
+     * Returns true if the user has any candidate authentication actions (locked credential
+     * supplier), and false otherwise.
+     */
+    public boolean hasAuthenticationResults() {
+        return mHasAuthenticationResults;
+    }
+
+    /**
+     * Returns true if the user has any candidate remote credential results, and false otherwise.
+     */
+    public boolean hasRemoteResults() {
+        return mHasRemoteResults;
+    }
+
+    /**
+     * Launches the necessary flows such as consent collection and credential selection to
+     * officially retrieve a credential among the pending credential candidates.
+     *
+     * @param activity           the activity used to launch any UI needed
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor           the callback will take place on this {@link Executor}
+     * @param callback           the callback invoked when the request succeeds or fails
+     */
+    public void show(@NonNull Activity activity, @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+        // TODO(b/273308895): implement
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mHasCredentialResults);
+        dest.writeBoolean(mHasAuthenticationResults);
+        dest.writeBoolean(mHasRemoteResults);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "GetCredentialResponse {" + "credential=" + mHasCredentialResults + "}";
+    }
+
+    /**
+     * Constructs a {@link GetPendingCredentialResponse}.
+     *
+     * @param hasCredentialResults whether the user has any candidate credentials
+     * @param hasAuthenticationResults whether the user has any candidate authentication actions
+     * @param hasRemoteResults whether the user has any candidate remote options
+     */
+    public GetPendingCredentialResponse(boolean hasCredentialResults,
+            boolean hasAuthenticationResults, boolean hasRemoteResults) {
+        mHasCredentialResults = hasCredentialResults;
+        mHasAuthenticationResults = hasAuthenticationResults;
+        mHasRemoteResults = hasRemoteResults;
+    }
+
+    private GetPendingCredentialResponse(@NonNull Parcel in) {
+        mHasCredentialResults = in.readBoolean();
+        mHasAuthenticationResults = in.readBoolean();
+        mHasRemoteResults = in.readBoolean();
+    }
+
+    public static final @NonNull Creator<GetPendingCredentialResponse> CREATOR = new Creator<>() {
+        @Override
+        public GetPendingCredentialResponse[] newArray(int size) {
+            return new GetPendingCredentialResponse[size];
+        }
+
+        @Override
+        public GetPendingCredentialResponse createFromParcel(@NonNull Parcel in) {
+            return new GetPendingCredentialResponse(in);
+        }
+    };
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 8c2cb5a..af8e7b4 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -27,6 +27,7 @@
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetPendingCredentialCallback;
 import android.credentials.ISetEnabledProvidersCallback;
 import android.content.ComponentName;
 import android.os.ICancellationSignal;
@@ -40,6 +41,8 @@
 
     @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
+    @nullable ICancellationSignal executeGetPendingCredential(in GetCredentialRequest request, in IGetPendingCredentialCallback callback, String callingPackage);
+
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
diff --git a/core/java/android/credentials/IGetPendingCredentialCallback.aidl b/core/java/android/credentials/IGetPendingCredentialCallback.aidl
new file mode 100644
index 0000000..4ab0f99
--- /dev/null
+++ b/core/java/android/credentials/IGetPendingCredentialCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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 android.credentials;
+
+import android.app.PendingIntent;
+import android.credentials.GetPendingCredentialResponse;
+
+/**
+ * Listener for a executeGetPendingCredential request.
+ *
+ * @hide
+ */
+interface IGetPendingCredentialCallback {
+    oneway void onResponse(in GetPendingCredentialResponse response);
+    oneway void onError(String errorType, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b9b310f..c95d081 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -4123,7 +4123,8 @@
      * counterparts.
      * This key will only be present for devices which advertise the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
      * <p><b>Units</b>: Pixel coordinates on the image sensor</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
@@ -4148,7 +4149,8 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
      * This key will only be present for devices which advertise the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
      * <p><b>Units</b>: Pixels</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
@@ -4172,7 +4174,8 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
      * This key will only be present for devices which advertise the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
      * <p><b>Units</b>: Pixel coordinates on the image sensor</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
@@ -4192,14 +4195,29 @@
      * to improve various aspects of imaging such as noise reduction, low light
      * performance etc. These groups can be of various sizes such as 2X2 (quad bayer),
      * 3X3 (nona-bayer). This key specifies the length and width of the pixels grouped under
-     * the same color filter.</p>
-     * <p>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW images
-     * will have a regular bayer pattern.</p>
-     * <p>This key will not be present for sensors which don't have the
-     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * the same color filter.
+     * In case the device has the
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability :</p>
+     * <ul>
+     * <li>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW
+     *   images will have a regular bayer pattern.</li>
+     * </ul>
+     * <p>In case the device does not have the
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability :</p>
+     * <ul>
+     * <li>This key will be present if
+     *   {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     *   lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}, since RAW
+     *   images may not necessarily have a regular bayer pattern when
+     *   {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}} is set to
+     *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</li>
+     * </ul>
      * <p><b>Units</b>: Pixels</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#SENSOR_PIXEL_MODE
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 722dd08..e6b3069 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -125,6 +125,23 @@
             "camera.enable_landscape_to_portrait";
 
     /**
+     * Enable physical camera availability callbacks when the logical camera is unavailable
+     *
+     * <p>Previously once a logical camera becomes unavailable, no {@link
+     * #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable} will be called until
+     * the logical camera becomes available again. The results in the app opening the logical
+     * camera not able to receive physical camera availability change.</p>
+     *
+     * <p>With this change, the {@link #onPhysicalCameraAvailable} and {@link
+     * #onPhysicalCameraUnavailable} can still be called while the logical camera is unavailable.
+     * </p>
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA =
+            244358506L;
+
+    /**
      * @hide
      */
     public CameraManager(Context context) {
@@ -1194,6 +1211,14 @@
     }
 
     /**
+     * @hide
+     */
+    public static boolean physicalCallbacksAreEnabledForUnavailableCamera() {
+        return CompatChanges.isChangeEnabled(
+                ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA);
+    }
+
+    /**
      * A callback for camera devices becoming available or unavailable to open.
      *
      * <p>Cameras become available when they are no longer in use, or when a new
@@ -1270,9 +1295,10 @@
          * to begin with, {@link #onPhysicalCameraUnavailable} may be invoked after
          * {@link #onCameraAvailable}.</p>
          *
-         * <p>Limitation: Opening a logical camera disables the {@link #onPhysicalCameraAvailable}
-         * and {@link #onPhysicalCameraUnavailable} callbacks for its physical cameras. For example,
-         * if app A opens the camera device:</p>
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &lt; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera
+         * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable}
+         * callbacks for its physical cameras. For example, if app A opens the camera device:</p>
          *
          * <ul>
          *
@@ -1284,6 +1310,33 @@
          *
          * </ul>
          *
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &ge; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p>
+         *
+         * <ul>
+         *
+         * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable}
+         * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes
+         * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the
+         * physical cameras' availability status. This makes it possible for an application opening
+         * the logical camera device to know which physical camera becomes unavailable or available
+         * to use.</li>
+         *
+         * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier,
+         * the logical camera's {@link #onCameraAvailable} callback implies all of its physical
+         * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called
+         * for any unavailable physical cameras upon the logical camera becoming available.</li>
+         *
+         * </ul>
+         *
+         * <p>Given the pipeline nature of the camera capture through {@link
+         * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application
+         * requests images from a physical camera of a logical multi-camera and that physical camera
+         * becomes unavailable. The application should stop requesting directly from an unavailable
+         * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be
+         * ready to robustly handle frame drop errors for requests targeting physical cameras,
+         * since those errors may arrive before the unavailability callback.</p>
+         *
          * <p>The default implementation of this method does nothing.</p>
          *
          * @param cameraId The unique identifier of the logical multi-camera.
@@ -1306,9 +1359,10 @@
          * cameras of its parent logical multi-camera, when {@link #onCameraUnavailable} for
          * the logical multi-camera is invoked.</p>
          *
-         * <p>Limitation: Opening a logical camera disables the {@link #onPhysicalCameraAvailable}
-         * and {@link #onPhysicalCameraUnavailable} callbacks for its physical cameras. For example,
-         * if app A opens the camera device:</p>
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &lt; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera
+         * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable}
+         * callbacks for its physical cameras. For example, if app A opens the camera device:</p>
          *
          * <ul>
          *
@@ -1320,6 +1374,33 @@
          *
          * </ul>
          *
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &ge; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p>
+         *
+         * <ul>
+         *
+         * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable}
+         * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes
+         * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the
+         * physical cameras' availability status. This makes it possible for an application opening
+         * the logical camera device to know which physical camera becomes unavailable or available
+         * to use.</li>
+         *
+         * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier,
+         * the logical camera's {@link #onCameraAvailable} callback implies all of its physical
+         * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called
+         * for any unavailable physical cameras upon the logical camera becoming available.</li>
+         *
+         * </ul>
+         *
+         * <p>Given the pipeline nature of the camera capture through {@link
+         * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application
+         * requests images from a physical camera of a logical multi-camera and that physical camera
+         * becomes unavailable. The application should stop requesting directly from an unavailable
+         * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be
+         * ready to robustly handle frame drop errors for requests targeting physical cameras,
+         * since those errors may arrive before the unavailability callback.</p>
+         *
          * <p>The default implementation of this method does nothing.</p>
          *
          * @param cameraId The unique identifier of the logical multi-camera.
@@ -2283,7 +2364,8 @@
                 postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
 
                 // Send the NOT_PRESENT state for unavailable physical cameras
-                if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
+                if ((isAvailable(status) || physicalCallbacksAreEnabledForUnavailableCamera())
+                        && mUnavailablePhysicalDevices.containsKey(id)) {
                     ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
                     for (String unavailableId : unavailableIds) {
                         postSingleUpdate(callback, executor, id, unavailableId,
@@ -2416,7 +2498,8 @@
                 return;
             }
 
-            if (!isAvailable(mDeviceStatus.get(id))) {
+            if (!physicalCallbacksAreEnabledForUnavailableCamera()
+                    && !isAvailable(mDeviceStatus.get(id))) {
                 Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
                         + "status change callback(s)", id));
                 return;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 705afc5..ed2a198 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3645,17 +3645,13 @@
     //
 
     /**
-     * <p>This is the default sensor pixel mode. This is the only sensor pixel mode
-     * supported unless a camera device advertises
-     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.</p>
+     * <p>This is the default sensor pixel mode.</p>
      * @see CaptureRequest#SENSOR_PIXEL_MODE
      */
     public static final int SENSOR_PIXEL_MODE_DEFAULT = 0;
 
     /**
-     * <p>This sensor pixel mode is offered by devices with capability
-     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.
-     * In this mode, sensors typically do not bin pixels, as a result can offer larger
+     * <p>In this mode, sensors typically do not bin pixels, as a result can offer larger
      * image sizes.</p>
      * @see CaptureRequest#SENSOR_PIXEL_MODE
      */
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 381c87d..929868b 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1430,7 +1430,9 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability,
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}
      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
@@ -1660,7 +1662,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -1882,7 +1887,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3169,7 +3177,9 @@
      * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
+     * <p>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3517,13 +3527,10 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * When operating in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
-     * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability would typically perform pixel binning in order to improve low light
+     * would typically perform pixel binning in order to improve low light
      * performance, noise reduction etc. However, in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     * mode (supported only
-     * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+     * mode, sensors typically operate in unbinned mode allowing for a larger image size.
      * The stream configurations supported in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
      * mode are also different from those of
@@ -3537,7 +3544,32 @@
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
      * must not be mixed in the same CaptureRequest. In other words, these outputs are
      * exclusive to each other.
-     * This key does not need to be set for reprocess requests.</p>
+     * This key does not need to be set for reprocess requests.
+     * This key will be be present on devices supporting the
+     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability. It may also be present on devices which do not support the aforementioned
+     * capability. In that case:</p>
+     * <ul>
+     * <li>
+     * <p>The mandatory stream combinations listed in
+     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
+     *   would not apply.</p>
+     * </li>
+     * <li>
+     * <p>The bayer pattern of {@code RAW} streams when
+     *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     * </li>
+     * <li>
+     * <p>The following keys will always be present:</p>
+     * <ul>
+     * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.pixelArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution}</li>
+     * </ul>
+     * </li>
+     * </ul>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
@@ -3548,6 +3580,9 @@
      *
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
      * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
      */
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 635e79c..a429f30 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -849,7 +849,9 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability,
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}
      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
@@ -1329,7 +1331,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -1962,7 +1967,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3831,7 +3839,9 @@
      * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
+     * <p>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -4442,13 +4452,10 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * When operating in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
-     * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability would typically perform pixel binning in order to improve low light
+     * would typically perform pixel binning in order to improve low light
      * performance, noise reduction etc. However, in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     * mode (supported only
-     * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+     * mode, sensors typically operate in unbinned mode allowing for a larger image size.
      * The stream configurations supported in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
      * mode are also different from those of
@@ -4462,7 +4469,32 @@
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
      * must not be mixed in the same CaptureRequest. In other words, these outputs are
      * exclusive to each other.
-     * This key does not need to be set for reprocess requests.</p>
+     * This key does not need to be set for reprocess requests.
+     * This key will be be present on devices supporting the
+     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability. It may also be present on devices which do not support the aforementioned
+     * capability. In that case:</p>
+     * <ul>
+     * <li>
+     * <p>The mandatory stream combinations listed in
+     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
+     *   would not apply.</p>
+     * </li>
+     * <li>
+     * <p>The bayer pattern of {@code RAW} streams when
+     *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     * </li>
+     * <li>
+     * <p>The following keys will always be present:</p>
+     * <ul>
+     * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.pixelArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution}</li>
+     * </ul>
+     * </li>
+     * </ul>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
@@ -4473,6 +4505,9 @@
      *
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
      * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
      */
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index db83e62..2fa8b87 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -101,15 +101,15 @@
 
 
     // Lock to synchronize cross-thread access to device public interface
-    final Object mInterfaceLock = new Object(); // access from this class and Session only!
+    final Object mInterfaceLock;
 
     /**
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.CAMERA)
     public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession(
-            @NonNull CameraDevice cameraDevice, @NonNull Context ctx,
-            @NonNull ExtensionSessionConfiguration config, int sessionId)
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
+            @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
             throws CameraAccessException, RemoteException {
         long clientId = CameraExtensionCharacteristics.registerClient(ctx);
         if (clientId < 0) {
@@ -209,7 +209,8 @@
     }
 
     private CameraAdvancedExtensionSessionImpl(long extensionClientId,
-            @NonNull IAdvancedExtenderImpl extender, @NonNull CameraDevice cameraDevice,
+            @NonNull IAdvancedExtenderImpl extender,
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
             @Nullable Surface postviewSurface,
             @NonNull CameraExtensionSession.StateCallback callback, @NonNull Executor executor,
@@ -228,6 +229,7 @@
         mInitialized = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mInterfaceLock = cameraDevice.mInterfaceLock;
     }
 
     /**
@@ -599,13 +601,14 @@
         public void onConfigured(@NonNull CameraCaptureSession session) {
             synchronized (mInterfaceLock) {
                 mCaptureSession = session;
-                try {
-                    CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to initialize session! Extension service does"
-                            + " not respond!");
-                    notifyConfigurationFailure();
-                }
+            }
+
+            try {
+                CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to initialize session! Extension service does"
+                        + " not respond!");
+                notifyConfigurationFailure();
             }
         }
     }
@@ -613,46 +616,56 @@
     private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
         @Override
         public void onSuccess() {
-            boolean status = true;
-            synchronized (mInterfaceLock) {
-                try {
-                    if (mSessionProcessor != null) {
-                        mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
-                        mInitialized = true;
-                    } else {
-                        Log.v(TAG, "Failed to start capture session, session released before " +
-                                "extension start!");
-                        status = false;
-                        mCaptureSession.close();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    boolean status = true;
+                    synchronized (mInterfaceLock) {
+                        try {
+                            if (mSessionProcessor != null) {
+                                mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
+                                mInitialized = true;
+                            } else {
+                                Log.v(TAG, "Failed to start capture session, session " +
+                                                " released before extension start!");
+                                status = false;
+                            }
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Failed to start capture session,"
+                                    + " extension service does not respond!");
+                            status = false;
+                            mInitialized = false;
+                        }
                     }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to start capture session,"
-                            + " extension service does not respond!");
-                    status = false;
-                    mCaptureSession.close();
-                }
-            }
 
-            if (status) {
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mExecutor.execute(
-                            () -> mCallbacks.onConfigured(CameraAdvancedExtensionSessionImpl.this));
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
+                    if (status) {
+                        final long ident = Binder.clearCallingIdentity();
+                        try {
+                            mExecutor.execute(() -> mCallbacks.onConfigured(
+                                    CameraAdvancedExtensionSessionImpl.this));
+                        } finally {
+                            Binder.restoreCallingIdentity(ident);
+                        }
+                    } else {
+                        onFailure();
+                    }
                 }
-            } else {
-                notifyConfigurationFailure();
-            }
+            });
         }
 
         @Override
         public void onFailure() {
-            mCaptureSession.close();
-            Log.e(TAG, "Failed to initialize proxy service session!"
-                    + " This can happen when trying to configure multiple "
-                    + "concurrent extension sessions!");
-            notifyConfigurationFailure();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCaptureSession.close();
+
+                    Log.e(TAG, "Failed to initialize proxy service session!"
+                            + " This can happen when trying to configure multiple "
+                            + "concurrent extension sessions!");
+                    notifyConfigurationFailure();
+                }
+            });
         }
     }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c2b3656..9c878c7 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -117,7 +117,7 @@
     private boolean mInternalRepeatingRequestEnabled = true;
 
     // Lock to synchronize cross-thread access to device public interface
-    final Object mInterfaceLock = new Object(); // access from this class and Session only!
+    final Object mInterfaceLock;
 
     private static int nativeGetSurfaceFormat(Surface surface) {
         return SurfaceUtils.getSurfaceFormat(surface);
@@ -128,7 +128,7 @@
      */
     @RequiresPermission(android.Manifest.permission.CAMERA)
     public static CameraExtensionSessionImpl createCameraExtensionSession(
-            @NonNull CameraDevice cameraDevice,
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @NonNull Context ctx,
             @NonNull ExtensionSessionConfiguration config,
             int sessionId)
@@ -251,7 +251,7 @@
             @NonNull IPreviewExtenderImpl previewExtender,
             @NonNull List<Size> previewSizes,
             long extensionClientId,
-            @NonNull CameraDevice cameraDevice,
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @Nullable Surface repeatingRequestSurface,
             @Nullable Surface burstCaptureSurface,
             @Nullable Surface postviewSurface,
@@ -279,6 +279,7 @@
         mSupportedRequestKeys = requestKeys;
         mSupportedResultKeys = resultKeys;
         mCaptureResultsSupported = !resultKeys.isEmpty();
+        mInterfaceLock = cameraDevice.mInterfaceLock;
     }
 
     private void initializeRepeatingRequestPipeline() throws RemoteException {
@@ -969,46 +970,56 @@
     private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
         @Override
         public void onSuccess() {
-            boolean status = true;
-            ArrayList<CaptureStageImpl> initialRequestList =
-                    compileInitialRequestList();
-            if (!initialRequestList.isEmpty()) {
-                try {
-                    setInitialCaptureRequest(initialRequestList,
-                            new InitialRequestHandler(
-                                    mRepeatingRequestImageCallback));
-                } catch (CameraAccessException e) {
-                    Log.e(TAG,
-                            "Failed to initialize the initial capture "
-                                    + "request!");
-                    status = false;
-                }
-            } else {
-                try {
-                    setRepeatingRequest(mPreviewExtender.getCaptureStage(),
-                            new PreviewRequestHandler(null, null, null,
-                                    mRepeatingRequestImageCallback));
-                } catch (CameraAccessException | RemoteException e) {
-                    Log.e(TAG,
-                            "Failed to initialize internal repeating "
-                                    + "request!");
-                    status = false;
-                }
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    boolean status = true;
+                    ArrayList<CaptureStageImpl> initialRequestList =
+                            compileInitialRequestList();
+                    if (!initialRequestList.isEmpty()) {
+                        try {
+                            setInitialCaptureRequest(initialRequestList,
+                                    new InitialRequestHandler(
+                                            mRepeatingRequestImageCallback));
+                        } catch (CameraAccessException e) {
+                            Log.e(TAG,
+                                    "Failed to initialize the initial capture "
+                                            + "request!");
+                            status = false;
+                        }
+                    } else {
+                        try {
+                            setRepeatingRequest(mPreviewExtender.getCaptureStage(),
+                                    new PreviewRequestHandler(null, null, null,
+                                            mRepeatingRequestImageCallback));
+                        } catch (CameraAccessException | RemoteException e) {
+                            Log.e(TAG,
+                                    "Failed to initialize internal repeating "
+                                            + "request!");
+                            status = false;
+                        }
 
-            }
+                    }
 
-            if (!status) {
-                notifyConfigurationFailure();
-            }
+                    if (!status) {
+                        notifyConfigurationFailure();
+                    }
+                }
+            });
         }
 
         @Override
         public void onFailure() {
-            mCaptureSession.close();
-            Log.e(TAG, "Failed to initialize proxy service session!"
-                    + " This can happen when trying to configure multiple "
-                    + "concurrent extension sessions!");
-            notifyConfigurationFailure();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCaptureSession.close();
+                    Log.e(TAG, "Failed to initialize proxy service session!"
+                            + " This can happen when trying to configure multiple "
+                            + "concurrent extension sessions!");
+                    notifyConfigurationFailure();
+                }
+            });
         }
     }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 631df01..9743c1f 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1618,15 +1618,20 @@
     }
 
     private StreamConfigurationMap getStreamConfigurationMapMaximumResolution() {
-        if (!isUltraHighResolutionSensor()) {
-            return null;
-        }
         StreamConfiguration[] configurations = getBase(
                 CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] minFrameDurations = getBase(
                 CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] stallDurations = getBase(
                 CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+        // If the at least these keys haven't been advertised, there cannot be a meaningful max
+        // resolution StreamConfigurationMap
+        if (configurations == null ||
+                minFrameDurations == null ||
+                stallDurations == null) {
+            return null;
+        }
+
         StreamConfiguration[] depthConfigurations = getBase(
                 CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] depthMinFrameDurations = getBase(
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 490589f..a1d640a 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -44,8 +44,6 @@
 import android.os.IBinder;
 import android.os.IVibratorStateListener;
 import android.os.InputEventInjectionSync;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -53,7 +51,6 @@
 import android.os.Vibrator;
 import android.os.VibratorManager;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
@@ -66,9 +63,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -88,10 +83,6 @@
     // To enable these logs, run: 'adb shell setprop log.tag.InputManager DEBUG' (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final int MSG_DEVICE_ADDED = 1;
-    private static final int MSG_DEVICE_REMOVED = 2;
-    private static final int MSG_DEVICE_CHANGED = 3;
-
     private static InputManager sInstance;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -112,32 +103,6 @@
     @Nullable
     private Boolean mIsStylusPointerIconEnabled = null;
 
-    // Guarded by mInputDevicesLock
-    private final Object mInputDevicesLock = new Object();
-    private SparseArray<InputDevice> mInputDevices;
-    private InputDevicesChangedListener mInputDevicesChangedListener;
-    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
-
-    // Guarded by mTabletModeLock
-    private final Object mTabletModeLock = new Object();
-    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
-    private TabletModeChangedListener mTabletModeChangedListener;
-    private ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
-
-    private final Object mBatteryListenersLock = new Object();
-    // Maps a deviceId whose battery is currently being monitored to an entry containing the
-    // registered listeners for that device.
-    @GuardedBy("mBatteryListenersLock")
-    private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
-    @GuardedBy("mBatteryListenersLock")
-    private IInputDeviceBatteryListener mInputDeviceBatteryListener;
-
-    private final Object mKeyboardBacklightListenerLock = new Object();
-    @GuardedBy("mKeyboardBacklightListenerLock")
-    private ArrayList<KeyboardBacklightListenerDelegate> mKeyboardBacklightListeners;
-    @GuardedBy("mKeyboardBacklightListenerLock")
-    private IKeyboardBacklightListener mKeyboardBacklightListener;
-
     private InputDeviceSensorManager mInputDeviceSensorManager;
     /**
      * Broadcast Action: Query available keyboard layouts.
@@ -403,27 +368,7 @@
      */
     @Nullable
     public InputDevice getInputDevice(int id) {
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            int index = mInputDevices.indexOfKey(id);
-            if (index < 0) {
-                return null;
-            }
-
-            InputDevice inputDevice = mInputDevices.valueAt(index);
-            if (inputDevice == null) {
-                try {
-                    inputDevice = mIm.getInputDevice(id);
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
-                if (inputDevice != null) {
-                    mInputDevices.setValueAt(index, inputDevice);
-                }
-            }
-            return inputDevice;
-        }
+        return mGlobal.getInputDevice(id);
     }
 
     /**
@@ -433,34 +378,7 @@
      * @hide
      */
     public InputDevice getInputDeviceByDescriptor(String descriptor) {
-        if (descriptor == null) {
-            throw new IllegalArgumentException("descriptor must not be null.");
-        }
-
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            int numDevices = mInputDevices.size();
-            for (int i = 0; i < numDevices; i++) {
-                InputDevice inputDevice = mInputDevices.valueAt(i);
-                if (inputDevice == null) {
-                    int id = mInputDevices.keyAt(i);
-                    try {
-                        inputDevice = mIm.getInputDevice(id);
-                    } catch (RemoteException ex) {
-                        throw ex.rethrowFromSystemServer();
-                    }
-                    if (inputDevice == null) {
-                        continue;
-                    }
-                    mInputDevices.setValueAt(i, inputDevice);
-                }
-                if (descriptor.equals(inputDevice.getDescriptor())) {
-                    return inputDevice;
-                }
-            }
-            return null;
-        }
+        return mGlobal.getInputDeviceByDescriptor(descriptor);
     }
 
     /**
@@ -468,16 +386,7 @@
      * @return The input device ids.
      */
     public int[] getInputDeviceIds() {
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            final int count = mInputDevices.size();
-            final int[] ids = new int[count];
-            for (int i = 0; i < count; i++) {
-                ids[i] = mInputDevices.keyAt(i);
-            }
-            return ids;
-        }
+        return mGlobal.getInputDeviceIds();
     }
 
     /**
@@ -547,17 +456,7 @@
      * @see #unregisterInputDeviceListener
      */
     public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-            int index = findInputDeviceListenerLocked(listener);
-            if (index < 0) {
-                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
-            }
-        }
+        mGlobal.registerInputDeviceListener(listener, handler);
     }
 
     /**
@@ -568,28 +467,7 @@
      * @see #registerInputDeviceListener
      */
     public void unregisterInputDeviceListener(InputDeviceListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mInputDevicesLock) {
-            int index = findInputDeviceListenerLocked(listener);
-            if (index >= 0) {
-                InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
-                d.removeCallbacksAndMessages(null);
-                mInputDeviceListeners.remove(index);
-            }
-        }
-    }
-
-    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
-        final int numListeners = mInputDeviceListeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            if (mInputDeviceListeners.get(i).mListener == listener) {
-                return i;
-            }
-        }
-        return -1;
+        mGlobal.unregisterInputDeviceListener(listener);
     }
 
     /**
@@ -618,20 +496,7 @@
      */
     public void registerOnTabletModeChangedListener(
             OnTabletModeChangedListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-        synchronized (mTabletModeLock) {
-            if (mOnTabletModeChangedListeners == null) {
-                initializeTabletModeListenerLocked();
-            }
-            int idx = findOnTabletModeChangedListenerLocked(listener);
-            if (idx < 0) {
-                OnTabletModeChangedListenerDelegate d =
-                    new OnTabletModeChangedListenerDelegate(listener, handler);
-                mOnTabletModeChangedListeners.add(d);
-            }
-        }
+        mGlobal.registerOnTabletModeChangedListener(listener, handler);
     }
 
     /**
@@ -641,37 +506,7 @@
      * @hide
      */
     public void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-        synchronized (mTabletModeLock) {
-            int idx = findOnTabletModeChangedListenerLocked(listener);
-            if (idx >= 0) {
-                OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
-                d.removeCallbacksAndMessages(null);
-            }
-        }
-    }
-
-    private void initializeTabletModeListenerLocked() {
-        final TabletModeChangedListener listener = new TabletModeChangedListener();
-        try {
-            mIm.registerTabletModeChangedListener(listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-        mTabletModeChangedListener = listener;
-        mOnTabletModeChangedListeners = new ArrayList<>();
-    }
-
-    private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
-        final int N = mOnTabletModeChangedListeners.size();
-        for (int i = 0; i < N; i++) {
-            if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
-                return i;
-            }
-        }
-        return -1;
+        mGlobal.unregisterOnTabletModeChangedListener(listener);
     }
 
     /**
@@ -1543,133 +1378,7 @@
      */
     @Nullable
     public HostUsiVersion getHostUsiVersion(@NonNull Display display) {
-        Objects.requireNonNull(display, "display should not be null");
-
-        // Return the first valid USI version reported by any input device associated with
-        // the display.
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            for (int i = 0; i < mInputDevices.size(); i++) {
-                final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
-                if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
-                    if (device.getHostUsiVersion() != null) {
-                        return device.getHostUsiVersion();
-                    }
-                }
-            }
-        }
-
-        // If there are no input devices that report a valid USI version, see if there is a config
-        // that specifies the USI version for the display. This is to handle cases where the USI
-        // input device is not registered by the kernel/driver all the time.
-        try {
-            return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private void populateInputDevicesLocked() {
-        if (mInputDevicesChangedListener == null) {
-            final InputDevicesChangedListener listener = new InputDevicesChangedListener();
-            try {
-                mIm.registerInputDevicesChangedListener(listener);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-            mInputDevicesChangedListener = listener;
-        }
-
-        if (mInputDevices == null) {
-            final int[] ids;
-            try {
-                ids = mIm.getInputDeviceIds();
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-
-            mInputDevices = new SparseArray<>();
-            for (int id : ids) {
-                mInputDevices.put(id, null);
-            }
-        }
-    }
-
-    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
-        if (DEBUG) {
-            Log.d(TAG, "Received input devices changed.");
-        }
-
-        synchronized (mInputDevicesLock) {
-            for (int i = mInputDevices.size(); --i > 0; ) {
-                final int deviceId = mInputDevices.keyAt(i);
-                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Device removed: " + deviceId);
-                    }
-                    mInputDevices.removeAt(i);
-                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
-                }
-            }
-
-            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
-                final int deviceId = deviceIdAndGeneration[i];
-                int index = mInputDevices.indexOfKey(deviceId);
-                if (index >= 0) {
-                    final InputDevice device = mInputDevices.valueAt(index);
-                    if (device != null) {
-                        final int generation = deviceIdAndGeneration[i + 1];
-                        if (device.getGeneration() != generation) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Device changed: " + deviceId);
-                            }
-                            mInputDevices.setValueAt(index, null);
-                            sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
-                        }
-                    }
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "Device added: " + deviceId);
-                    }
-                    mInputDevices.put(deviceId, null);
-                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
-                }
-            }
-        }
-    }
-
-    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
-        final int numListeners = mInputDeviceListeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
-            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
-        }
-    }
-
-    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
-        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
-            if (deviceIdAndGeneration[i] == deviceId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-
-    private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
-        if (DEBUG) {
-            Log.d(TAG, "Received tablet mode changed: "
-                    + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
-        }
-        synchronized (mTabletModeLock) {
-            final int numListeners = mOnTabletModeChangedListeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                OnTabletModeChangedListenerDelegate listener =
-                        mOnTabletModeChangedListeners.get(i);
-                listener.sendTabletModeChanged(whenNanos, inTabletMode);
-            }
-        }
+        return mGlobal.getHostUsiVersion(display);
     }
 
     /**
@@ -1808,15 +1517,7 @@
      */
     @NonNull
     public BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
-        if (!hasBattery) {
-            return new LocalBatteryState();
-        }
-        try {
-            final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
-            return new LocalBatteryState(state.isPresent, state.status, state.capacity);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.getInputDeviceBatteryState(deviceId, hasBattery);
     }
 
     /**
@@ -1952,49 +1653,7 @@
      */
     public void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
             @NonNull InputDeviceBatteryListener listener) {
-        Objects.requireNonNull(executor, "executor should not be null");
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mBatteryListenersLock) {
-            if (mBatteryListeners == null) {
-                mBatteryListeners = new SparseArray<>();
-                mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
-            }
-            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
-            if (listenersForDevice == null) {
-                // The deviceId is currently not being monitored for battery changes.
-                // Start monitoring the device.
-                listenersForDevice = new RegisteredBatteryListeners();
-                mBatteryListeners.put(deviceId, listenersForDevice);
-                try {
-                    mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            } else {
-                // The deviceId is already being monitored for battery changes.
-                // Ensure that the listener is not already registered.
-                final int numDelegates = listenersForDevice.mDelegates.size();
-                for (int i = 0; i < numDelegates; i++) {
-                    InputDeviceBatteryListener registeredListener =
-                            listenersForDevice.mDelegates.get(i).mListener;
-                    if (Objects.equals(listener, registeredListener)) {
-                        throw new IllegalArgumentException(
-                                "Attempting to register an InputDeviceBatteryListener that has "
-                                        + "already been registered for deviceId: "
-                                        + deviceId);
-                    }
-                }
-            }
-            final InputDeviceBatteryListenerDelegate delegate =
-                    new InputDeviceBatteryListenerDelegate(listener, executor);
-            listenersForDevice.mDelegates.add(delegate);
-
-            // Notify the listener immediately if we already have the latest battery state.
-            if (listenersForDevice.mInputDeviceBatteryState != null) {
-                delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
-            }
-        }
+        mGlobal.addInputDeviceBatteryListener(deviceId, executor, listener);
     }
 
     /**
@@ -2004,44 +1663,7 @@
      */
     public void removeInputDeviceBatteryListener(int deviceId,
             @NonNull InputDeviceBatteryListener listener) {
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mBatteryListenersLock) {
-            if (mBatteryListeners == null) {
-                return;
-            }
-            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
-            if (listenersForDevice == null) {
-                // The deviceId is not currently being monitored.
-                return;
-            }
-            final List<InputDeviceBatteryListenerDelegate> delegates =
-                    listenersForDevice.mDelegates;
-            for (int i = 0; i < delegates.size();) {
-                if (Objects.equals(listener, delegates.get(i).mListener)) {
-                    delegates.remove(i);
-                    continue;
-                }
-                i++;
-            }
-            if (!delegates.isEmpty()) {
-                return;
-            }
-
-            // There are no more battery listeners for this deviceId. Stop monitoring this device.
-            mBatteryListeners.remove(deviceId);
-            try {
-                mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            if (mBatteryListeners.size() == 0) {
-                // There are no more devices being monitored, so the registered
-                // IInputDeviceBatteryListener will be automatically dropped by the server.
-                mBatteryListeners = null;
-                mInputDeviceBatteryListener = null;
-            }
-        }
+        mGlobal.removeInputDeviceBatteryListener(deviceId, listener);
     }
 
     /**
@@ -2067,30 +1689,7 @@
     @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
     public void registerKeyboardBacklightListener(@NonNull Executor executor,
             @NonNull KeyboardBacklightListener listener) throws IllegalArgumentException {
-        Objects.requireNonNull(executor, "executor should not be null");
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mKeyboardBacklightListenerLock) {
-            if (mKeyboardBacklightListener == null) {
-                mKeyboardBacklightListeners = new ArrayList<>();
-                mKeyboardBacklightListener = new LocalKeyboardBacklightListener();
-
-                try {
-                    mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
-            final int numListeners = mKeyboardBacklightListeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                if (mKeyboardBacklightListeners.get(i).mListener == listener) {
-                    throw new IllegalArgumentException("Listener has already been registered!");
-                }
-            }
-            KeyboardBacklightListenerDelegate delegate =
-                    new KeyboardBacklightListenerDelegate(listener, executor);
-            mKeyboardBacklightListeners.add(delegate);
-        }
+        mGlobal.registerKeyboardBacklightListener(executor, listener);
     }
 
     /**
@@ -2103,23 +1702,7 @@
     @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
     public void unregisterKeyboardBacklightListener(
             @NonNull KeyboardBacklightListener listener) {
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mKeyboardBacklightListenerLock) {
-            if (mKeyboardBacklightListeners == null) {
-                return;
-            }
-            mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener);
-            if (mKeyboardBacklightListeners.isEmpty()) {
-                try {
-                    mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-                mKeyboardBacklightListeners = null;
-                mKeyboardBacklightListener = null;
-            }
-        }
+        mGlobal.unregisterKeyboardBacklightListener(listener);
     }
 
     /**
@@ -2149,7 +1732,7 @@
     public interface InputDeviceListener {
         /**
          * Called whenever an input device has been added to the system.
-         * Use {@link InputManager#getInputDevice} to get more information about the device.
+         * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
          *
          * @param deviceId The id of the input device that was added.
          */
@@ -2172,37 +1755,6 @@
         void onInputDeviceChanged(int deviceId);
     }
 
-    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
-        @Override
-        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
-            InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
-        }
-    }
-
-    private static final class InputDeviceListenerDelegate extends Handler {
-        public final InputDeviceListener mListener;
-
-        public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
-            mListener = listener;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DEVICE_ADDED:
-                    mListener.onInputDeviceAdded(msg.arg1);
-                    break;
-                case MSG_DEVICE_REMOVED:
-                    mListener.onInputDeviceRemoved(msg.arg1);
-                    break;
-                case MSG_DEVICE_CHANGED:
-                    mListener.onInputDeviceChanged(msg.arg1);
-                    break;
-            }
-        }
-    }
-
     /** @hide */
     public interface OnTabletModeChangedListener {
         /**
@@ -2235,170 +1787,4 @@
         void onKeyboardBacklightChanged(
                 int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
     }
-
-    private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
-        @Override
-        public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
-            InputManager.this.onTabletModeChanged(whenNanos, inTabletMode);
-        }
-    }
-
-    private static final class OnTabletModeChangedListenerDelegate extends Handler {
-        private static final int MSG_TABLET_MODE_CHANGED = 0;
-
-        public final OnTabletModeChangedListener mListener;
-
-        public OnTabletModeChangedListenerDelegate(
-                OnTabletModeChangedListener listener, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
-            mListener = listener;
-        }
-
-        public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = (int) whenNanos;
-            args.argi2 = (int) (whenNanos >> 32);
-            args.arg1 = inTabletMode;
-            obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_TABLET_MODE_CHANGED) {
-                SomeArgs args = (SomeArgs) msg.obj;
-                long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
-                boolean inTabletMode = (boolean) args.arg1;
-                mListener.onTabletModeChanged(whenNanos, inTabletMode);
-            }
-        }
-    }
-
-    // Implementation of the android.hardware.BatteryState interface used to report the battery
-    // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
-    private static final class LocalBatteryState extends BatteryState {
-        private final boolean mIsPresent;
-        private final int mStatus;
-        private final float mCapacity;
-
-        LocalBatteryState() {
-            this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
-        }
-
-        LocalBatteryState(boolean isPresent, int status, float capacity) {
-            mIsPresent = isPresent;
-            mStatus = status;
-            mCapacity = capacity;
-        }
-
-        @Override
-        public boolean isPresent() {
-            return mIsPresent;
-        }
-
-        @Override
-        public int getStatus() {
-            return mStatus;
-        }
-
-        @Override
-        public float getCapacity() {
-            return mCapacity;
-        }
-    }
-
-    private static final class RegisteredBatteryListeners {
-        final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
-        IInputDeviceBatteryState mInputDeviceBatteryState;
-    }
-
-    private static final class InputDeviceBatteryListenerDelegate {
-        final InputDeviceBatteryListener mListener;
-        final Executor mExecutor;
-
-        InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
-            mListener = listener;
-            mExecutor = executor;
-        }
-
-        void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
-            mExecutor.execute(() ->
-                    mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
-                            new LocalBatteryState(state.isPresent, state.status, state.capacity)));
-        }
-    }
-
-    private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
-        @Override
-        public void onBatteryStateChanged(IInputDeviceBatteryState state) {
-            synchronized (mBatteryListenersLock) {
-                if (mBatteryListeners == null) return;
-                final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
-                if (entry == null) return;
-
-                entry.mInputDeviceBatteryState = state;
-                final int numDelegates = entry.mDelegates.size();
-                for (int i = 0; i < numDelegates; i++) {
-                    entry.mDelegates.get(i)
-                            .notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
-                }
-            }
-        }
-    }
-
-    // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report
-    // the keyboard backlight state via the KeyboardBacklightListener interfaces.
-    private static final class LocalKeyboardBacklightState extends KeyboardBacklightState {
-
-        private final int mBrightnessLevel;
-        private final int mMaxBrightnessLevel;
-
-        LocalKeyboardBacklightState(int brightnessLevel, int maxBrightnessLevel) {
-            mBrightnessLevel = brightnessLevel;
-            mMaxBrightnessLevel = maxBrightnessLevel;
-        }
-
-        @Override
-        public int getBrightnessLevel() {
-            return mBrightnessLevel;
-        }
-
-        @Override
-        public int getMaxBrightnessLevel() {
-            return mMaxBrightnessLevel;
-        }
-    }
-
-    private static final class KeyboardBacklightListenerDelegate {
-        final KeyboardBacklightListener mListener;
-        final Executor mExecutor;
-
-        KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) {
-            mListener = listener;
-            mExecutor = executor;
-        }
-
-        void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state,
-                boolean isTriggeredByKeyPress) {
-            mExecutor.execute(() ->
-                    mListener.onKeyboardBacklightChanged(deviceId,
-                            new LocalKeyboardBacklightState(state.brightnessLevel,
-                                    state.maxBrightnessLevel), isTriggeredByKeyPress));
-        }
-    }
-
-    private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub {
-
-        @Override
-        public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state,
-                boolean isTriggeredByKeyPress) {
-            synchronized (mKeyboardBacklightListenerLock) {
-                if (mKeyboardBacklightListeners == null) return;
-                final int numListeners = mKeyboardBacklightListeners.size();
-                for (int i = 0; i < numListeners; i++) {
-                    mKeyboardBacklightListeners.get(i)
-                            .notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress);
-                }
-            }
-        }
-    }
 }
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 82dddfc..3a6df84 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -16,18 +16,71 @@
 
 package android.hardware.input;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.hardware.BatteryState;
+import android.hardware.input.InputManager.InputDeviceBatteryListener;
+import android.hardware.input.InputManager.InputDeviceListener;
+import android.hardware.input.InputManager.KeyboardBacklightListener;
+import android.hardware.input.InputManager.OnTabletModeChangedListener;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Manages communication with the input manager service on behalf of
- * an application process.  You're probably looking for {@link InputManager}.
+ * an application process. You're probably looking for {@link InputManager}.
  *
  * @hide
  */
 public final class InputManagerGlobal {
     private static final String TAG = "InputManagerGlobal";
+    // To enable these logs, run: 'adb shell setprop log.tag.InputManagerGlobal DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @GuardedBy("mInputDeviceListeners")
+    @Nullable private SparseArray<InputDevice> mInputDevices;
+    @GuardedBy("mInputDeviceListeners")
+    @Nullable private InputDevicesChangedListener mInputDevicesChangedListener;
+    @GuardedBy("mInputDeviceListeners")
+    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private final ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners =
+            new ArrayList<>();
+
+    private final Object mBatteryListenersLock = new Object();
+    // Maps a deviceId whose battery is currently being monitored to an entry containing the
+    // registered listeners for that device.
+    @GuardedBy("mBatteryListenersLock")
+    @Nullable private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
+    @GuardedBy("mBatteryListenersLock")
+    @Nullable private IInputDeviceBatteryListener mInputDeviceBatteryListener;
+
+    private final Object mKeyboardBacklightListenerLock = new Object();
+    @GuardedBy("mKeyboardBacklightListenerLock")
+    @Nullable private ArrayList<KeyboardBacklightListenerDelegate> mKeyboardBacklightListeners;
+    @GuardedBy("mKeyboardBacklightListenerLock")
+    @Nullable private IKeyboardBacklightListener mKeyboardBacklightListener;
 
     private static InputManagerGlobal sInstance;
 
@@ -79,4 +132,693 @@
             sInstance = null;
         }
     }
+
+    /**
+     * @see InputManager#getInputDevice(int)
+     */
+    @Nullable
+    public InputDevice getInputDevice(int id) {
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            int index = mInputDevices.indexOfKey(id);
+            if (index < 0) {
+                return null;
+            }
+
+            InputDevice inputDevice = mInputDevices.valueAt(index);
+            if (inputDevice == null) {
+                try {
+                    inputDevice = mIm.getInputDevice(id);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+                if (inputDevice != null) {
+                    mInputDevices.setValueAt(index, inputDevice);
+                }
+            }
+            return inputDevice;
+        }
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private void populateInputDevicesLocked() {
+        if (mInputDevicesChangedListener == null) {
+            final InputDevicesChangedListener
+                    listener = new InputDevicesChangedListener();
+            try {
+                mIm.registerInputDevicesChangedListener(listener);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+            mInputDevicesChangedListener = listener;
+        }
+
+        if (mInputDevices == null) {
+            final int[] ids;
+            try {
+                ids = mIm.getInputDeviceIds();
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+
+            mInputDevices = new SparseArray<>();
+            for (int id : ids) {
+                mInputDevices.put(id, null);
+            }
+        }
+    }
+
+    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+        @Override
+        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+            InputManagerGlobal.this.onInputDevicesChanged(deviceIdAndGeneration);
+        }
+    }
+
+    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+        if (DEBUG) {
+            Log.d(TAG, "Received input devices changed.");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            for (int i = mInputDevices.size(); --i > 0; ) {
+                final int deviceId = mInputDevices.keyAt(i);
+                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device removed: " + deviceId);
+                    }
+                    mInputDevices.removeAt(i);
+                    sendMessageToInputDeviceListenersLocked(
+                            InputDeviceListenerDelegate.MSG_DEVICE_REMOVED, deviceId);
+                }
+            }
+
+            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+                final int deviceId = deviceIdAndGeneration[i];
+                int index = mInputDevices.indexOfKey(deviceId);
+                if (index >= 0) {
+                    final InputDevice device = mInputDevices.valueAt(index);
+                    if (device != null) {
+                        final int generation = deviceIdAndGeneration[i + 1];
+                        if (device.getGeneration() != generation) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Device changed: " + deviceId);
+                            }
+                            mInputDevices.setValueAt(index, null);
+                            sendMessageToInputDeviceListenersLocked(
+                                    InputDeviceListenerDelegate.MSG_DEVICE_CHANGED, deviceId);
+                        }
+                    }
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device added: " + deviceId);
+                    }
+                    mInputDevices.put(deviceId, null);
+                    sendMessageToInputDeviceListenersLocked(
+                            InputDeviceListenerDelegate.MSG_DEVICE_ADDED, deviceId);
+                }
+            }
+        }
+    }
+
+    private static final class InputDeviceListenerDelegate extends Handler {
+        public final InputDeviceListener mListener;
+        static final int MSG_DEVICE_ADDED = 1;
+        static final int MSG_DEVICE_REMOVED = 2;
+        static final int MSG_DEVICE_CHANGED = 3;
+
+        InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DEVICE_ADDED:
+                    mListener.onInputDeviceAdded(msg.arg1);
+                    break;
+                case MSG_DEVICE_REMOVED:
+                    mListener.onInputDeviceRemoved(msg.arg1);
+                    break;
+                case MSG_DEVICE_CHANGED:
+                    mListener.onInputDeviceChanged(msg.arg1);
+                    break;
+            }
+        }
+    }
+
+    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+            if (deviceIdAndGeneration[i] == deviceId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+        }
+    }
+
+    /**
+     * @see InputManager#registerInputDeviceListener
+     */
+    void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+            int index = findInputDeviceListenerLocked(listener);
+            if (index < 0) {
+                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterInputDeviceListener
+     */
+    void unregisterInputDeviceListener(InputDeviceListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            int index = findInputDeviceListenerLocked(listener);
+            if (index >= 0) {
+                InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
+                d.removeCallbacksAndMessages(null);
+                mInputDeviceListeners.remove(index);
+            }
+        }
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            if (mInputDeviceListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @see InputManager#getInputDeviceIds
+     */
+    public int[] getInputDeviceIds() {
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            final int count = mInputDevices.size();
+            final int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices.keyAt(i);
+            }
+            return ids;
+        }
+    }
+
+    /**
+     * @see InputManager#getInputDeviceByDescriptor
+     */
+    InputDevice getInputDeviceByDescriptor(String descriptor) {
+        if (descriptor == null) {
+            throw new IllegalArgumentException("descriptor must not be null.");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            int numDevices = mInputDevices.size();
+            for (int i = 0; i < numDevices; i++) {
+                InputDevice inputDevice = mInputDevices.valueAt(i);
+                if (inputDevice == null) {
+                    int id = mInputDevices.keyAt(i);
+                    try {
+                        inputDevice = mIm.getInputDevice(id);
+                    } catch (RemoteException ex) {
+                        throw ex.rethrowFromSystemServer();
+                    }
+                    if (inputDevice == null) {
+                        continue;
+                    }
+                    mInputDevices.setValueAt(i, inputDevice);
+                }
+                if (descriptor.equals(inputDevice.getDescriptor())) {
+                    return inputDevice;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * @see InputManager#getHostUsiVersion
+     */
+    @Nullable
+    HostUsiVersion getHostUsiVersion(@NonNull Display display) {
+        Objects.requireNonNull(display, "display should not be null");
+
+        // Return the first valid USI version reported by any input device associated with
+        // the display.
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            for (int i = 0; i < mInputDevices.size(); i++) {
+                final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
+                if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
+                    if (device.getHostUsiVersion() != null) {
+                        return device.getHostUsiVersion();
+                    }
+                }
+            }
+        }
+
+        // If there are no input devices that report a valid USI version, see if there is a config
+        // that specifies the USI version for the display. This is to handle cases where the USI
+        // input device is not registered by the kernel/driver all the time.
+        try {
+            return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+        if (DEBUG) {
+            Log.d(TAG, "Received tablet mode changed: "
+                    + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            final int numListeners = mOnTabletModeChangedListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                OnTabletModeChangedListenerDelegate listener =
+                        mOnTabletModeChangedListeners.get(i);
+                listener.sendTabletModeChanged(whenNanos, inTabletMode);
+            }
+        }
+    }
+
+    private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
+        @Override
+        public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+            InputManagerGlobal.this.onTabletModeChanged(whenNanos, inTabletMode);
+        }
+    }
+
+    private static final class OnTabletModeChangedListenerDelegate extends Handler {
+        private static final int MSG_TABLET_MODE_CHANGED = 0;
+
+        public final OnTabletModeChangedListener mListener;
+
+        OnTabletModeChangedListenerDelegate(
+                OnTabletModeChangedListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = (int) whenNanos;
+            args.argi2 = (int) (whenNanos >> 32);
+            args.arg1 = inTabletMode;
+            obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_TABLET_MODE_CHANGED) {
+                SomeArgs args = (SomeArgs) msg.obj;
+                long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
+                boolean inTabletMode = (boolean) args.arg1;
+                mListener.onTabletModeChanged(whenNanos, inTabletMode);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#registerInputDeviceListener(InputDeviceListener, Handler)
+     */
+    void registerOnTabletModeChangedListener(
+            OnTabletModeChangedListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            if (mOnTabletModeChangedListeners == null) {
+                initializeTabletModeListenerLocked();
+            }
+            int idx = findOnTabletModeChangedListenerLocked(listener);
+            if (idx < 0) {
+                OnTabletModeChangedListenerDelegate d =
+                        new OnTabletModeChangedListenerDelegate(listener, handler);
+                mOnTabletModeChangedListeners.add(d);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterOnTabletModeChangedListener(OnTabletModeChangedListener)
+     */
+    void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            int idx = findOnTabletModeChangedListenerLocked(listener);
+            if (idx >= 0) {
+                OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
+                d.removeCallbacksAndMessages(null);
+            }
+        }
+    }
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private void initializeTabletModeListenerLocked() {
+        final TabletModeChangedListener listener = new TabletModeChangedListener();
+        try {
+            mIm.registerTabletModeChangedListener(listener);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
+        final int n = mOnTabletModeChangedListeners.size();
+        for (int i = 0; i < n; i++) {
+            if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static final class RegisteredBatteryListeners {
+        final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
+        IInputDeviceBatteryState mInputDeviceBatteryState;
+    }
+
+    private static final class InputDeviceBatteryListenerDelegate {
+        final InputDeviceBatteryListener mListener;
+        final Executor mExecutor;
+
+        InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
+            mExecutor.execute(() ->
+                    mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
+                            new LocalBatteryState(state.isPresent, state.status, state.capacity)));
+        }
+    }
+
+    /**
+     * @see InputManager#addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+     */
+    void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
+            @NonNull InputDeviceBatteryListener listener) {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mBatteryListenersLock) {
+            if (mBatteryListeners == null) {
+                mBatteryListeners = new SparseArray<>();
+                mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
+            }
+            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+            if (listenersForDevice == null) {
+                // The deviceId is currently not being monitored for battery changes.
+                // Start monitoring the device.
+                listenersForDevice = new RegisteredBatteryListeners();
+                mBatteryListeners.put(deviceId, listenersForDevice);
+                try {
+                    mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            } else {
+                // The deviceId is already being monitored for battery changes.
+                // Ensure that the listener is not already registered.
+                final int numDelegates = listenersForDevice.mDelegates.size();
+                for (int i = 0; i < numDelegates; i++) {
+                    InputDeviceBatteryListener registeredListener =
+                            listenersForDevice.mDelegates.get(i).mListener;
+                    if (Objects.equals(listener, registeredListener)) {
+                        throw new IllegalArgumentException(
+                                "Attempting to register an InputDeviceBatteryListener that has "
+                                        + "already been registered for deviceId: "
+                                        + deviceId);
+                    }
+                }
+            }
+            final InputDeviceBatteryListenerDelegate delegate =
+                    new InputDeviceBatteryListenerDelegate(listener, executor);
+            listenersForDevice.mDelegates.add(delegate);
+
+            // Notify the listener immediately if we already have the latest battery state.
+            if (listenersForDevice.mInputDeviceBatteryState != null) {
+                delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+     */
+    void removeInputDeviceBatteryListener(int deviceId,
+            @NonNull InputDeviceBatteryListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mBatteryListenersLock) {
+            if (mBatteryListeners == null) {
+                return;
+            }
+            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+            if (listenersForDevice == null) {
+                // The deviceId is not currently being monitored.
+                return;
+            }
+            final List<InputDeviceBatteryListenerDelegate> delegates =
+                    listenersForDevice.mDelegates;
+            for (int i = 0; i < delegates.size();) {
+                if (Objects.equals(listener, delegates.get(i).mListener)) {
+                    delegates.remove(i);
+                    continue;
+                }
+                i++;
+            }
+            if (!delegates.isEmpty()) {
+                return;
+            }
+
+            // There are no more battery listeners for this deviceId. Stop monitoring this device.
+            mBatteryListeners.remove(deviceId);
+            try {
+                mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            if (mBatteryListeners.size() == 0) {
+                // There are no more devices being monitored, so the registered
+                // IInputDeviceBatteryListener will be automatically dropped by the server.
+                mBatteryListeners = null;
+                mInputDeviceBatteryListener = null;
+            }
+        }
+    }
+
+    private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
+        @Override
+        public void onBatteryStateChanged(IInputDeviceBatteryState state) {
+            synchronized (mBatteryListenersLock) {
+                if (mBatteryListeners == null) return;
+                final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
+                if (entry == null) return;
+
+                entry.mInputDeviceBatteryState = state;
+                final int numDelegates = entry.mDelegates.size();
+                for (int i = 0; i < numDelegates; i++) {
+                    entry.mDelegates.get(i)
+                            .notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
+                }
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#getInputDeviceBatteryState(int, boolean)
+     */
+    @NonNull
+    BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
+        if (!hasBattery) {
+            return new LocalBatteryState();
+        }
+        try {
+            final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
+            return new LocalBatteryState(state.isPresent, state.status, state.capacity);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    // Implementation of the android.hardware.BatteryState interface used to report the battery
+    // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
+    private static final class LocalBatteryState extends BatteryState {
+        private final boolean mIsPresent;
+        private final int mStatus;
+        private final float mCapacity;
+
+        LocalBatteryState() {
+            this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
+        }
+
+        LocalBatteryState(boolean isPresent, int status, float capacity) {
+            mIsPresent = isPresent;
+            mStatus = status;
+            mCapacity = capacity;
+        }
+
+        @Override
+        public boolean isPresent() {
+            return mIsPresent;
+        }
+
+        @Override
+        public int getStatus() {
+            return mStatus;
+        }
+
+        @Override
+        public float getCapacity() {
+            return mCapacity;
+        }
+    }
+
+    private static final class KeyboardBacklightListenerDelegate {
+        final InputManager.KeyboardBacklightListener mListener;
+        final Executor mExecutor;
+
+        KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state,
+                boolean isTriggeredByKeyPress) {
+            mExecutor.execute(() ->
+                    mListener.onKeyboardBacklightChanged(deviceId,
+                            new LocalKeyboardBacklightState(state.brightnessLevel,
+                                    state.maxBrightnessLevel), isTriggeredByKeyPress));
+        }
+    }
+
+    private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub {
+
+        @Override
+        public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state,
+                boolean isTriggeredByKeyPress) {
+            synchronized (mKeyboardBacklightListenerLock) {
+                if (mKeyboardBacklightListeners == null) return;
+                final int numListeners = mKeyboardBacklightListeners.size();
+                for (int i = 0; i < numListeners; i++) {
+                    mKeyboardBacklightListeners.get(i)
+                            .notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress);
+                }
+            }
+        }
+    }
+
+    // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report
+    // the keyboard backlight state via the KeyboardBacklightListener interfaces.
+    private static final class LocalKeyboardBacklightState extends KeyboardBacklightState {
+
+        private final int mBrightnessLevel;
+        private final int mMaxBrightnessLevel;
+
+        LocalKeyboardBacklightState(int brightnessLevel, int maxBrightnessLevel) {
+            mBrightnessLevel = brightnessLevel;
+            mMaxBrightnessLevel = maxBrightnessLevel;
+        }
+
+        @Override
+        public int getBrightnessLevel() {
+            return mBrightnessLevel;
+        }
+
+        @Override
+        public int getMaxBrightnessLevel() {
+            return mMaxBrightnessLevel;
+        }
+    }
+
+    /**
+     * @see InputManager#registerKeyboardBacklightListener(Executor, KeyboardBacklightListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+    void registerKeyboardBacklightListener(@NonNull Executor executor,
+            @NonNull KeyboardBacklightListener listener) throws IllegalArgumentException {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardBacklightListenerLock) {
+            if (mKeyboardBacklightListener == null) {
+                mKeyboardBacklightListeners = new ArrayList<>();
+                mKeyboardBacklightListener = new LocalKeyboardBacklightListener();
+
+                try {
+                    mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            final int numListeners = mKeyboardBacklightListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                if (mKeyboardBacklightListeners.get(i).mListener == listener) {
+                    throw new IllegalArgumentException("Listener has already been registered!");
+                }
+            }
+            KeyboardBacklightListenerDelegate delegate =
+                    new KeyboardBacklightListenerDelegate(listener, executor);
+            mKeyboardBacklightListeners.add(delegate);
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterKeyboardBacklightListener(KeyboardBacklightListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+    void unregisterKeyboardBacklightListener(
+            @NonNull KeyboardBacklightListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardBacklightListenerLock) {
+            if (mKeyboardBacklightListeners == null) {
+                return;
+            }
+            mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener);
+            if (mKeyboardBacklightListeners.isEmpty()) {
+                try {
+                    mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mKeyboardBacklightListeners = null;
+                mKeyboardBacklightListener = null;
+            }
+        }
+    }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9689be2..244632a 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -64,17 +64,27 @@
     /**
      * The product name for attestation. In non-default builds (like the AOSP build) the value of
      * the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product name, it's running on.
+     * and Keymint attestation would still attest to the product name which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String PRODUCT_FOR_ATTESTATION =
-            getString("ro.product.name_for_attestation");
+    public static final String PRODUCT_FOR_ATTESTATION = getVendorDeviceIdProperty("name");
 
     /** The name of the industrial design. */
     public static final String DEVICE = getString("ro.product.device");
 
+    /**
+     * The device name for attestation. In non-default builds (like the AOSP build) the value of
+     * the 'DEVICE' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the device name which was provisioned.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String DEVICE_FOR_ATTESTATION =
+            getVendorDeviceIdProperty("device");
+
     /** The name of the underlying board, like "goldfish". */
     public static final String BOARD = getString("ro.product.board");
 
@@ -97,19 +107,29 @@
     /** The manufacturer of the product/hardware. */
     public static final String MANUFACTURER = getString("ro.product.manufacturer");
 
+    /**
+     * The manufacturer name for attestation. In non-default builds (like the AOSP build) the value
+     * of the 'MANUFACTURER' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the manufacturer which was provisioned.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String MANUFACTURER_FOR_ATTESTATION =
+            getVendorDeviceIdProperty("manufacturer");
+
     /** The consumer-visible brand with which the product/hardware will be associated, if any. */
     public static final String BRAND = getString("ro.product.brand");
 
     /**
      * The product brand for attestation. In non-default builds (like the AOSP build) the value of
      * the 'BRAND' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product brand, it's running on.
+     * and Keymint attestation would still attest to the product brand which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String BRAND_FOR_ATTESTATION =
-                getString("ro.product.brand_for_attestation");
+    public static final String BRAND_FOR_ATTESTATION = getVendorDeviceIdProperty("brand");
 
     /** The end-user-visible name for the end product. */
     public static final String MODEL = getString("ro.product.model");
@@ -117,13 +137,12 @@
     /**
      * The product model for attestation. In non-default builds (like the AOSP build) the value of
      * the 'MODEL' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product model, it's running on.
+     * and Keymint attestation would still attest to the product model which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String MODEL_FOR_ATTESTATION =
-                getString("ro.product.model_for_attestation");
+    public static final String MODEL_FOR_ATTESTATION = getVendorDeviceIdProperty("model");
 
     /** The manufacturer of the device's primary system-on-chip. */
     @NonNull
@@ -1527,6 +1546,17 @@
     private static String getString(String property) {
         return SystemProperties.get(property, UNKNOWN);
     }
+    /**
+     * Return attestation specific proerties.
+     * @param property model, name, brand, device or manufacturer.
+     * @return property value or UNKNOWN
+     */
+    private static String getVendorDeviceIdProperty(String property) {
+        String attestProp = getString(
+                TextUtils.formatSimple("ro.product.%s_for_attestation", property));
+        return attestProp.equals(UNKNOWN)
+                ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : UNKNOWN;
+    }
 
     private static String[] getStringList(String property, String separator) {
         String value = SystemProperties.get(property);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index ac1583a..b2208d1 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -365,6 +365,11 @@
     public static final int LAST_APPLICATION_CACHE_GID = 29999;
 
     /**
+     * An invalid PID value.
+     */
+    public static final int INVALID_PID = -1;
+
+    /**
      * Standard priority of application threads.
      * Use with {@link #setThreadPriority(int)} and
      * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 290f929..86e678d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5601,16 +5601,15 @@
      *
      * @see #KEY_RESTRICTIONS_PENDING
      *
-     * @deprecated Use
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+     * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+     * set by all managing apps, use
      * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
-     * Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, it is
-     * possible for there to be multiple managing agents on the device with the ability to set
-     * restrictions. This API will only to return the restrictions set by device policy controllers
-     * (DPCs)
      *
      * @see DevicePolicyManager
      */
-    @Deprecated
     @WorkerThread
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public Bundle getApplicationRestrictions(String packageName) {
@@ -5623,12 +5622,15 @@
     }
 
     /**
-     * @deprecated Use
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+     * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+     * set by all managing apps, use
      * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      *
      * @hide
      */
-    @Deprecated
     @WorkerThread
     public Bundle getApplicationRestrictions(String packageName, UserHandle user) {
         try {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07d265b..127c7a0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9541,6 +9541,14 @@
         public static final String SCREENSAVER_COMPLICATIONS_ENABLED =
                 "screensaver_complications_enabled";
 
+        /**
+         * Whether home controls are enabled to be shown over the screensaver by the user.
+         *
+         * @hide
+         */
+        public static final String SCREENSAVER_HOME_CONTROLS_ENABLED =
+                "screensaver_home_controls_enabled";
+
 
         /**
          * Default, indicates that the user has not yet started the dock setup flow.
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index ce8af83..d0f3820 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1362,6 +1362,11 @@
                 if (!ActivityTaskManager.getService().startDreamActivity(i)) {
                     detach();
                 }
+            } catch (SecurityException e) {
+                Log.w(mTag,
+                        "Received SecurityException trying to start DreamActivity. "
+                        + "Aborting dream start.");
+                detach();
             } catch (RemoteException e) {
                 Log.w(mTag, "Could not connect to activity task manager to start dream activity");
                 e.rethrowFromSystemServer();
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index 3134dcd..1148fe3 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -78,23 +78,10 @@
      * information see {@link #checkRecognitionSupport},  {@link #startListening} and
      * {@link RecognizerIntent}.
      *
-     * Progress can be monitord by calling {@link #setModelDownloadListener} before a trigger.
+     * Progress updates can be received via {@link #IModelDownloadListener}.
      */
-    void triggerModelDownload(in Intent recognizerIntent, in AttributionSource attributionSource);
-
-    /**
-     * Sets listener to received download progress updates. Clients still have to call
-     * {@link #triggerModelDownload} to trigger a model download.
-     */
-    void setModelDownloadListener(
+    void triggerModelDownload(
         in Intent recognizerIntent,
         in AttributionSource attributionSource,
         in IModelDownloadListener listener);
-
-    /**
-     * Clears the listener for model download events attached to a recognitionIntent if any.
-     */
-    void clearModelDownloadListener(
-        in Intent recognizerIntent,
-        in AttributionSource attributionSource);
 }
diff --git a/core/java/android/speech/ModelDownloadListener.java b/core/java/android/speech/ModelDownloadListener.java
index 6c24399..a58ec90c 100644
--- a/core/java/android/speech/ModelDownloadListener.java
+++ b/core/java/android/speech/ModelDownloadListener.java
@@ -22,20 +22,27 @@
  */
 public interface ModelDownloadListener {
     /**
-     * Called by {@link RecognitionService} when there's an update on the download progress.
+     * Called by {@link RecognitionService} only if the download has started after the request.
      *
-     * <p>RecognitionService will call this zero or more times during the download.</p>
+     * <p> The number of calls to this method varies depending of the {@link RecognitionService}
+     * implementation. If the download finished quickly enough, {@link #onSuccess()} may be called
+     * directly. In other cases, this method may be called any number of times during the download.
+     *
+     * @param completedPercent the percentage of download that is completed
      */
     void onProgress(int completedPercent);
 
     /**
-     * Called when {@link RecognitionService} completed the download and it can now be used to
-     * satisfy recognition requests.
+     * This method is called:
+     * <li> if the model is already available;
+     * <li> if the {@link RecognitionService} has started and completed the download.
+     *
+     * <p> Once this method is called, the model can be safely used to satisfy recognition requests.
      */
     void onSuccess();
 
     /**
-     * Called when {@link RecognitionService} scheduled the download but won't satisfy it
+     * Called when {@link RecognitionService} scheduled the download, but won't satisfy it
      * immediately. There will be no further updates on this listener.
      */
     void onScheduled();
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 4ecec8f..9656f36 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -36,9 +36,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.util.function.pooled.PooledLambda;
 
@@ -93,10 +91,6 @@
 
     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6;
 
-    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 7;
-
-    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 8;
-
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -120,21 +114,11 @@
                             checkArgs.mIntent, checkArgs.callback, checkArgs.mAttributionSource);
                     break;
                 case MSG_TRIGGER_MODEL_DOWNLOAD:
-                    Pair<Intent, AttributionSource> params =
-                            (Pair<Intent, AttributionSource>) msg.obj;
-                    dispatchTriggerModelDownload(params.first, params.second);
-                    break;
-                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
-                    ModelDownloadListenerArgs dListenerArgs = (ModelDownloadListenerArgs) msg.obj;
-                    dispatchSetModelDownloadListener(
-                            dListenerArgs.mIntent,
-                            dListenerArgs.mListener,
-                            dListenerArgs.mAttributionSource);
-                    break;
-                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
-                    Pair<Intent, AttributionSource> clearDlPair =
-                            (Pair<Intent, AttributionSource>) msg.obj;
-                    dispatchClearModelDownloadListener(clearDlPair.first, clearDlPair.second);
+                    ModelDownloadArgs modelDownloadArgs = (ModelDownloadArgs) msg.obj;
+                    dispatchTriggerModelDownload(
+                            modelDownloadArgs.mIntent,
+                            modelDownloadArgs.mAttributionSource,
+                            modelDownloadArgs.mListener);
                     break;
             }
         }
@@ -239,59 +223,52 @@
 
     private void dispatchTriggerModelDownload(
             Intent intent,
-            AttributionSource attributionSource) {
-        RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
-    }
-
-    private void dispatchSetModelDownloadListener(
-            Intent intent,
-            IModelDownloadListener listener,
-            AttributionSource attributionSource) {
-        RecognitionService.this.setModelDownloadListener(
-                intent,
-                attributionSource,
-                new ModelDownloadListener() {
-                    @Override
-                    public void onProgress(int completedPercent) {
-                        try {
-                            listener.onProgress(completedPercent);
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+            AttributionSource attributionSource,
+            IModelDownloadListener listener) {
+        if (listener == null) {
+            RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
+        } else {
+            RecognitionService.this.onTriggerModelDownload(
+                    intent,
+                    attributionSource,
+                    new ModelDownloadListener() {
+                        @Override
+                        public void onProgress(int completedPercent) {
+                            try {
+                                listener.onProgress(completedPercent);
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onSuccess() {
-                        try {
-                            listener.onSuccess();
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onSuccess() {
+                            try {
+                                listener.onSuccess();
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onScheduled() {
-                        try {
-                            listener.onScheduled();
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onScheduled() {
+                            try {
+                                listener.onScheduled();
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onError(int error) {
-                        try {
-                            listener.onError(error);
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onError(int error) {
+                            try {
+                                listener.onError(error);
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
-                });
-    }
-
-    private void dispatchClearModelDownloadListener(
-            Intent intent, AttributionSource attributionSource) {
-        RecognitionService.this.clearModelDownloadListener(intent, attributionSource);
+                    });
+        }
     }
 
     private static class StartListeningArgs {
@@ -323,17 +300,18 @@
         }
     }
 
-    private static class ModelDownloadListenerArgs {
+    private static class ModelDownloadArgs {
         final Intent mIntent;
-        final IModelDownloadListener mListener;
         final AttributionSource mAttributionSource;
+        @Nullable final IModelDownloadListener mListener;
 
-        private ModelDownloadListenerArgs(Intent intent,
-                IModelDownloadListener listener,
-                AttributionSource attributionSource) {
-            mIntent = intent;
+        private ModelDownloadArgs(
+                Intent intent,
+                AttributionSource attributionSource,
+                @Nullable IModelDownloadListener listener) {
+            this.mIntent = intent;
+            this.mAttributionSource = attributionSource;
             this.mListener = listener;
-            mAttributionSource = attributionSource;
         }
     }
 
@@ -443,38 +421,39 @@
     }
 
     /**
-     * Sets a {@link ModelDownloadListener} to receive progress updates after
-     * {@link #onTriggerModelDownload} calls.
+     * Requests the download of the recognizer support for {@code recognizerIntent}.
      *
-     * @param recognizerIntent the request to monitor model download progress for.
-     * @param modelDownloadListener the listener to keep updated.
+     * <p> Provides the calling {@link AttributionSource} to the service implementation so that
+     * permissions and bandwidth could be correctly blamed.
+     *
+     * <p> Client will receive the progress updates via the given {@link ModelDownloadListener}:
+     *
+     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+     * called directly. The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has started the download,
+     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+     * number of times until the download is complete.
+     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+     * The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+     * There will be no further updates on this listener.
+     *
+     * <li> If the request fails at any time due to a network or scheduling error,
+     * {@link ModelDownloadListener#onError(int)} will be called.
+     *
+     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+     *        may also contain optional extras, see {@link RecognizerIntent}.
+     * @param attributionSource the attribution source of the caller.
+     * @param listener on which to receive updates about the model download request.
      */
-    public void setModelDownloadListener(
+    public void onTriggerModelDownload(
             @NonNull Intent recognizerIntent,
             @NonNull AttributionSource attributionSource,
-            @NonNull ModelDownloadListener modelDownloadListener) {
-        if (DBG) {
-            Log.i(TAG, TextUtils.formatSimple(
-                    "#setModelDownloadListener [%s] [%s]",
-                    recognizerIntent,
-                    modelDownloadListener));
-        }
-        modelDownloadListener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
-    }
-
-    /**
-     * Clears the {@link ModelDownloadListener} set to receive progress updates for the given
-     * {@code recognizerIntent}, if any.
-     *
-     * @param recognizerIntent the request to monitor model download progress for.
-     */
-    public void clearModelDownloadListener(
-            @NonNull Intent recognizerIntent,
-            @NonNull AttributionSource attributionSource) {
-        if (DBG) {
-            Log.i(TAG, TextUtils.formatSimple(
-                    "#clearModelDownloadListener [%s]", recognizerIntent));
-        }
+            @NonNull ModelDownloadListener listener) {
+        listener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
     }
 
     @Override
@@ -815,41 +794,18 @@
 
         @Override
         public void triggerModelDownload(
-                Intent recognizerIntent, @NonNull AttributionSource attributionSource) {
+                Intent recognizerIntent,
+                @NonNull AttributionSource attributionSource,
+                IModelDownloadListener listener) {
             final RecognitionService service = mServiceRef.get();
             if (service != null) {
                 service.mHandler.sendMessage(
                         Message.obtain(
                                 service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
-                                Pair.create(recognizerIntent, attributionSource)));
-            }
-        }
-
-        @Override
-        public void setModelDownloadListener(
-                Intent recognizerIntent,
-                AttributionSource attributionSource,
-                IModelDownloadListener listener) throws RemoteException {
-            final RecognitionService service = mServiceRef.get();
-            if (service != null) {
-                service.mHandler.sendMessage(
-                        Message.obtain(service.mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
-                                new ModelDownloadListenerArgs(
+                                new ModelDownloadArgs(
                                         recognizerIntent,
-                                        listener,
-                                        attributionSource)));
-            }
-        }
-
-        @Override
-        public void clearModelDownloadListener(
-                Intent recognizerIntent,
-                AttributionSource attributionSource) throws RemoteException {
-            final RecognitionService service = mServiceRef.get();
-            if (service != null) {
-                service.mHandler.sendMessage(
-                        Message.obtain(service.mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER,
-                                Pair.create(recognizerIntent, attributionSource)));
+                                        attributionSource,
+                                        listener)));
             }
         }
 
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 76eb09e..dacb25c 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -297,8 +297,6 @@
     private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
     private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
-    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 8;
-    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 9;
 
     /** The actual RecognitionService endpoint */
     private IRecognitionService mService;
@@ -341,19 +339,13 @@
                             args.mIntent, args.mCallbackExecutor, args.mCallback);
                     break;
                 case MSG_TRIGGER_MODEL_DOWNLOAD:
-                    handleTriggerModelDownload((Intent) msg.obj);
-                    break;
-                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
                     ModelDownloadListenerArgs modelDownloadListenerArgs =
                             (ModelDownloadListenerArgs) msg.obj;
-                    handleSetModelDownloadListener(
+                    handleTriggerModelDownload(
                             modelDownloadListenerArgs.mIntent,
                             modelDownloadListenerArgs.mExecutor,
                             modelDownloadListenerArgs.mModelDownloadListener);
                     break;
-                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
-                    handleClearModelDownloadListener((Intent) msg.obj);
-                    break;
             }
         }
     };
@@ -657,17 +649,13 @@
      * user interaction to approve the download. Callers can verify the status of the request via
      * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
      *
-     * <p>Listeners set via
-     * {@link #setModelDownloadListener(Intent, Executor, ModelDownloadListener)} will receive
-     * updates about this download request.</p>
-     *
      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
      *        may also contain optional extras, see {@link RecognizerIntent}.
      */
     public void triggerModelDownload(@NonNull Intent recognizerIntent) {
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
         if (DBG) {
-            Slog.i(TAG, "#triggerModelDownload called");
+            Slog.i(TAG, "#triggerModelDownload without a listener called");
             if (mService == null) {
                 Slog.i(TAG, "Connection is not established yet");
             }
@@ -676,23 +664,47 @@
             // First time connection: first establish a connection, then dispatch.
             connectToSystemService();
         }
-        putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
+        putMessage(Message.obtain(
+                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
+                new ModelDownloadListenerArgs(recognizerIntent, null, null)));
     }
 
     /**
-     * Sets a listener to model download updates. Clients will have to call this method before
-     * {@link #triggerModelDownload(Intent)}.
+     * Attempts to download the support for the given {@code recognizerIntent}. This might trigger
+     * user interaction to approve the download. Callers can verify the status of the request via
+     * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
      *
-     * @param recognizerIntent the request to monitor support for.
+     * <p> The updates about the model download request are received via the given
+     * {@link ModelDownloadListener}:
+     *
+     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+     * called directly. The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has started the download,
+     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+     * number of times until the download is complete.
+     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+     * The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+     * There will be no further updates on this listener.
+     *
+     * <li> If the request fails at any time due to a network or scheduling error,
+     * {@link ModelDownloadListener#onError(int)} will be called.
+     *
+     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+     *        may also contain optional extras, see {@link RecognizerIntent}.
+     * @param executor for dispatching listener callbacks
      * @param listener on which to receive updates about the model download request.
      */
-    public void setModelDownloadListener(
+    public void triggerModelDownload(
             @NonNull Intent recognizerIntent,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull ModelDownloadListener listener) {
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
         if (DBG) {
-            Slog.i(TAG, "#setModelDownloadListener called");
+            Slog.i(TAG, "#triggerModelDownload with a listener called");
             if (mService == null) {
                 Slog.i(TAG, "Connection is not established yet");
             }
@@ -702,32 +714,11 @@
             connectToSystemService();
         }
         putMessage(Message.obtain(
-                mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
+                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
                 new ModelDownloadListenerArgs(recognizerIntent, executor, listener)));
     }
 
     /**
-     * Clears the listener for model download updates if any.
-     *
-     * @param recognizerIntent the request to monitor support for.
-     */
-    public void clearModelDownloadListener(@NonNull Intent recognizerIntent) {
-        Objects.requireNonNull(recognizerIntent, "intent must not be null");
-        if (DBG) {
-            Slog.i(TAG, "#clearModelDownloadListener called");
-            if (mService == null) {
-                Slog.i(TAG, "Connection is not established yet");
-            }
-        }
-        if (mService == null) {
-            // First time connection: first establish a connection, then dispatch.
-            connectToSystemService();
-        }
-        putMessage(Message.obtain(
-                mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER, recognizerIntent));
-    }
-
-    /**
      * Sets a temporary component to power on-device speech recognizer.
      *
      * <p>This is only expected to be called in tests, system would reject calls from client apps.
@@ -836,51 +827,36 @@
         }
     }
 
-    private void handleTriggerModelDownload(Intent recognizerIntent) {
-        if (!maybeInitializeManagerService()) {
-            return;
-        }
-        try {
-            mService.triggerModelDownload(recognizerIntent, mContext.getAttributionSource());
-        } catch (final RemoteException e) {
-            Log.e(TAG, "downloadModel() failed", e);
-            mListener.onError(ERROR_CLIENT);
-        }
-    }
-
-    private void handleSetModelDownloadListener(
+    private void handleTriggerModelDownload(
             Intent recognizerIntent,
-            Executor callbackExecutor,
+            @Nullable Executor callbackExecutor,
             @Nullable ModelDownloadListener modelDownloadListener) {
         if (!maybeInitializeManagerService()) {
             return;
         }
-        try {
-            InternalModelDownloadListener listener =
-                    modelDownloadListener == null
-                            ? null
-                            : new InternalModelDownloadListener(
-                                    callbackExecutor,
-                                    modelDownloadListener);
-            mService.setModelDownloadListener(
-                    recognizerIntent, mContext.getAttributionSource(), listener);
-            if (DBG) Log.d(TAG, "setModelDownloadListener()");
-        } catch (final RemoteException e) {
-            Log.e(TAG, "setModelDownloadListener() failed", e);
-            callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
-        }
-    }
 
-    private void handleClearModelDownloadListener(Intent recognizerIntent) {
-        if (!maybeInitializeManagerService()) {
-            return;
+        // Trigger model download without a listener.
+        if (modelDownloadListener == null) {
+            try {
+                mService.triggerModelDownload(
+                        recognizerIntent, mContext.getAttributionSource(), null);
+                if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
+            } catch (final RemoteException e) {
+                Log.e(TAG, "triggerModelDownload() without a listener failed", e);
+                mListener.onError(ERROR_CLIENT);
+            }
         }
-        try {
-            mService.clearModelDownloadListener(
-                    recognizerIntent, mContext.getAttributionSource());
-            if (DBG) Log.d(TAG, "clearModelDownloadListener()");
-        } catch (final RemoteException e) {
-            Log.e(TAG, "clearModelDownloadListener() failed", e);
+        // Trigger model download with a listener.
+        else {
+            try {
+                mService.triggerModelDownload(
+                        recognizerIntent, mContext.getAttributionSource(),
+                        new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
+                if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
+            } catch (final RemoteException e) {
+                Log.e(TAG, "triggerModelDownload() with a listener failed", e);
+                callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
+            }
         }
     }
 
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
new file mode 100644
index 0000000..9f11e31
--- /dev/null
+++ b/core/java/android/text/TextFlags.java
@@ -0,0 +1,49 @@
+/*
+ * 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.text;
+
+/**
+ * Flags in the "text" namespace.
+ *
+ * @hide
+ */
+public final class TextFlags {
+
+    /**
+     * The name space of the "text" feature.
+     *
+     * This needs to move to DeviceConfig constant.
+     */
+    public static final String NAMESPACE = "text";
+
+    /**
+     * Whether we use the new design of context menu.
+     */
+    public static final String ENABLE_NEW_CONTEXT_MENU =
+            "TextEditing__enable_new_context_menu";
+
+    /**
+     * The key name used in app core settings for {@link #ENABLE_NEW_CONTEXT_MENU}.
+     */
+    public static final String KEY_ENABLE_NEW_CONTEXT_MENU = "text__enable_new_context_menu";
+
+    /**
+     * Default value for the flag {@link #ENABLE_NEW_CONTEXT_MENU}.
+     */
+    public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = false;
+
+}
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index b4a1e8c..c43864d 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -362,6 +362,15 @@
 
                 return true;
             }
+        } else if (keyCode == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
+            // If user is in the process of composing with a dead key, and
+            // presses Escape, cancel it. We need special handling because
+            // the Escape key will not produce a Unicode character
+            if (activeStart == selStart && activeEnd == selEnd) {
+                Selection.setSelection(content, selEnd);
+                content.removeSpan(TextKeyListener.ACTIVE);
+                return true;
+            }
         }
 
         return super.onKeyDown(view, content, keyCode, event);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2ae882c..6201b3a 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -220,9 +220,9 @@
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
         DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index a47783c..6b60442 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -205,6 +205,16 @@
     }
 
     /**
+     * Notify HandwritingInitiator that a delegate view (see {@link View#isHandwritingDelegate})
+     * gained focus.
+     */
+    public void onDelegateViewFocused(@NonNull View view) {
+        if (view == getConnectedView()) {
+            tryAcceptStylusHandwritingDelegation(view);
+        }
+    }
+
+    /**
      * Notify HandwritingInitiator that a new InputConnection is created.
      * The caller of this method should guarantee that each onInputConnectionCreated call
      * is paired with a onInputConnectionClosed call.
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 720f569..9225cd9 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -29,6 +29,7 @@
 import android.hardware.input.HostUsiVersion;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.hardware.lights.LightsManager;
 import android.icu.util.ULocale;
 import android.os.Build;
@@ -742,7 +743,7 @@
      */
     @Nullable
     public static InputDevice getDevice(int id) {
-        return InputManager.getInstance().getInputDevice(id);
+        return InputManagerGlobal.getInstance().getInputDevice(id);
     }
 
     /**
@@ -750,7 +751,7 @@
      * @return The input device ids.
      */
     public static int[] getDeviceIds() {
-        return InputManager.getInstance().getInputDeviceIds();
+        return InputManagerGlobal.getInstance().getInputDeviceIds();
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 800fc97..aec3487 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8324,6 +8324,13 @@
             onFocusLost();
         } else if (hasWindowFocus()) {
             notifyFocusChangeToImeFocusController(true /* hasFocus */);
+
+            if (mIsHandwritingDelegate) {
+                ViewRootImpl viewRoot = getViewRootImpl();
+                if (viewRoot != null) {
+                    viewRoot.getHandwritingInitiator().onDelegateViewFocused(this);
+                }
+            }
         }
 
         invalidate(true);
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index c96d298..d80819f 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -28,6 +28,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -1188,7 +1189,7 @@
     }
 
     private static boolean isInputDeviceInfoValid(int id, int axis, int source) {
-        InputDevice device = InputManager.getInstance().getInputDevice(id);
+        InputDevice device = InputManagerGlobal.getInstance().getInputDevice(id);
         return device != null && device.getMotionRange(axis, source) != null;
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 24dcb69..fb25e7a6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11626,7 +11626,8 @@
 
         mNumPausedForSync++;
         mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT);
-        mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT, 1000);
+        mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT,
+                1000 * Build.HW_TIMEOUT_MULTIPLIER);
         return mActiveSurfaceSyncGroup;
     };
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 9f9a781..dce5432 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -74,6 +74,7 @@
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.StaticLayout;
+import android.text.TextFlags;
 import android.text.TextUtils;
 import android.text.method.InsertModeTransformationMethod;
 import android.text.method.KeyListener;
@@ -169,9 +170,6 @@
     private static final String TAG = "Editor";
     private static final boolean DEBUG_UNDO = false;
 
-    // TODO(nona): Make this configurable.
-    private static final boolean FLAG_USE_NEW_CONTEXT_MENU = false;
-
     // Specifies whether to use the magnifier when pressing the insertion or selection handles.
     private static final boolean FLAG_USE_MAGNIFIER = true;
 
@@ -470,6 +468,7 @@
     private static final int LINE_CHANGE_SLOP_MIN_DP = 8;
     private int mLineChangeSlopMax;
     private int mLineChangeSlopMin;
+    private boolean mUseNewContextMenu;
 
     private final AccessibilitySmartActions mA11ySmartActions;
     private InsertModeController mInsertModeController;
@@ -500,6 +499,9 @@
         mLineSlopRatio = AppGlobals.getFloatCoreSetting(
                 WidgetFlags.KEY_LINE_SLOP_RATIO,
                 WidgetFlags.LINE_SLOP_RATIO_DEFAULT);
+        mUseNewContextMenu = AppGlobals.getIntCoreSetting(
+                TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+                TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
         if (TextView.DEBUG_CURSOR) {
             logCursor("Editor", "Cursor drag from anywhere is %s.",
                     mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
@@ -3171,7 +3173,7 @@
         final int menuItemOrderSelectAll;
         final int menuItemOrderShare;
         final int menuItemOrderAutofill;
-        if (FLAG_USE_NEW_CONTEXT_MENU) {
+        if (mUseNewContextMenu) {
             menuItemOrderPasteAsPlainText = 7;
             menuItemOrderSelectAll = 8;
             menuItemOrderShare = 9;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1600a16..fd80981 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -462,12 +462,12 @@
     private static final int CHANGE_WATCHER_PRIORITY = 100;
 
     /**
-     * The span priority of the {@link TransformationMethod} that is set on the text. It must be
+     * The span priority of the {@link OffsetMapping} that is set on the text. It must be
      * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
      * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
      * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
      */
-    private static final int TRANSFORMATION_SPAN_PRIORITY = 200;
+    private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200;
 
     // New state used to change background based on whether this TextView is multiline.
     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
@@ -7033,9 +7033,9 @@
         }
 
         final int textLength = text.length();
+        final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
 
-        if (text instanceof Spannable && (!mAllowTransformationLengthChange
-                || text instanceof OffsetMapping)) {
+        if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
             Spannable sp = (Spannable) text;
 
             // Remove any ChangeWatchers that might have come from other TextViews.
@@ -7053,8 +7053,9 @@
             if (mEditor != null) mEditor.addSpanWatchers(sp);
 
             if (mTransformation != null) {
+                final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
-                        | (TRANSFORMATION_SPAN_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
+                        | (priority << Spanned.SPAN_PRIORITY_SHIFT));
             }
 
             if (mMovement != null) {
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 071c20f..52e17ca 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -34,6 +34,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
@@ -223,6 +224,11 @@
                         PixelFormat.RGBA_8888,
                         GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
                                 | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+                if (background == null) {
+                    Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for "
+                            + mTitle);
+                    return;
+                }
                 // TODO: Support this on HardwareBuffer
                 final Canvas c = background.lockCanvas();
                 drawBackgroundAndBars(c, frame);
@@ -410,6 +416,7 @@
         layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
 
         layoutParams.setTitle(title);
+        layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
         return layoutParams;
     }
 
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 0672d63..7f99fb7 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -21,6 +21,7 @@
 import android.annotation.UiThread;
 import android.os.Binder;
 import android.os.BinderProxy;
+import android.os.Build;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -62,7 +63,7 @@
     private static final int MAX_COUNT = 100;
 
     private static final AtomicInteger sCounter = new AtomicInteger(0);
-    private static final int TRANSACTION_READY_TIMEOUT = 1000;
+    private static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER;
 
     private static Supplier<Transaction> sTransactionFactory = Transaction::new;
 
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index b8bd703..4c482460 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -24,6 +24,7 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -58,6 +59,7 @@
  * @hide
  */
 public final class TransitionInfo implements Parcelable {
+    private static final String TAG = "TransitionInfo";
 
     /**
      * Modes are only a sub-set of all the transit-types since they are per-container
@@ -184,9 +186,7 @@
     private final @TransitionType int mType;
     private final @TransitionFlags int mFlags;
     private final ArrayList<Change> mChanges = new ArrayList<>();
-
-    private SurfaceControl mRootLeash;
-    private final Point mRootOffset = new Point();
+    private final ArrayList<Root> mRoots = new ArrayList<>();
 
     private AnimationOptions mOptions;
 
@@ -200,10 +200,7 @@
         mType = in.readInt();
         mFlags = in.readInt();
         in.readTypedList(mChanges, Change.CREATOR);
-        mRootLeash = new SurfaceControl();
-        mRootLeash.readFromParcel(in);
-        mRootLeash.setUnreleasedWarningCallSite("TransitionInfo");
-        mRootOffset.readFromParcel(in);
+        in.readTypedList(mRoots, Root.CREATOR);
         mOptions = in.readTypedObject(AnimationOptions.CREATOR);
     }
 
@@ -213,8 +210,7 @@
         dest.writeInt(mType);
         dest.writeInt(mFlags);
         dest.writeTypedList(mChanges);
-        mRootLeash.writeToParcel(dest, flags);
-        mRootOffset.writeToParcel(dest, flags);
+        dest.writeTypedList(mRoots, flags);
         dest.writeTypedObject(mOptions, flags);
     }
 
@@ -238,10 +234,15 @@
         return 0;
     }
 
-    /** @see #getRootLeash() */
-    public void setRootLeash(@NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
-        mRootLeash = leash;
-        mRootOffset.set(offsetLeft, offsetTop);
+    /** @see #getRoot */
+    public void addRootLeash(int displayId, @NonNull SurfaceControl leash,
+            int offsetLeft, int offsetTop) {
+        mRoots.add(new Root(displayId, leash, offsetLeft, offsetTop));
+    }
+
+    /** @see #getRoot */
+    public void addRoot(Root other) {
+        mRoots.add(other);
     }
 
     public void setAnimationOptions(AnimationOptions options) {
@@ -257,23 +258,52 @@
     }
 
     /**
+     * @return The number of animation roots. Most transitions should have 1, but there may be more
+     *         in some cases (such as a transition spanning multiple displays).
+     */
+    public int getRootCount() {
+        return mRoots.size();
+    }
+
+    /**
+     * @return the transition-root at a specific index.
+     */
+    @NonNull
+    public Root getRoot(int idx) {
+        return mRoots.get(idx);
+    }
+
+    /**
+     * @return the index of the transition-root associated with `displayId` or -1 if not found.
+     */
+    public int findRootIndex(int displayId) {
+        for (int i = 0; i < mRoots.size(); ++i) {
+            if (mRoots.get(i).mDisplayId == displayId) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
      * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
      * participants to animate within. This will generally be placed at the highest-z-order
      * shared ancestor of all participants. While this is non-null, it's possible for the rootleash
      * to be invalid if the transition is a no-op.
+     *
+     * @deprecated Use {@link #getRoot} instead. This call assumes there is only one root.
      */
+    @Deprecated
     @NonNull
     public SurfaceControl getRootLeash() {
-        if (mRootLeash == null) {
-            throw new IllegalStateException("Trying to get a leash which wasn't set");
+        if (mRoots.isEmpty()) {
+            throw new IllegalStateException("Trying to get a root leash from a no-op transition.");
         }
-        return mRootLeash;
-    }
-
-    /** @return the offset (relative to the screen) of the root leash. */
-    @NonNull
-    public Point getRootOffset() {
-        return mRootOffset;
+        if (mRoots.size() > 1) {
+            android.util.Log.e(TAG, "Assuming one animation root when there are more.",
+                    new Throwable());
+        }
+        return mRoots.get(0).mLeash;
     }
 
     public AnimationOptions getAnimationOptions() {
@@ -320,8 +350,15 @@
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        sb.append("{t=" + transitTypeToString(mType) + " f=0x" + Integer.toHexString(mFlags)
-                + " ro=" + mRootOffset + " c=[");
+        sb.append("{t=").append(transitTypeToString(mType)).append(" f=0x")
+                .append(Integer.toHexString(mFlags)).append(" r=[");
+        for (int i = 0; i < mRoots.size(); ++i) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
+        }
+        sb.append("] c=[");
         for (int i = 0; i < mChanges.size(); ++i) {
             if (i > 0) {
                 sb.append(',');
@@ -448,8 +485,8 @@
                 c.mSnapshot = null;
             }
         }
-        if (mRootLeash != null) {
-            mRootLeash.release();
+        for (int i = 0; i < mRoots.size(); ++i) {
+            mRoots.get(i).mLeash.release();
         }
     }
 
@@ -476,10 +513,11 @@
         for (int i = 0; i < mChanges.size(); ++i) {
             out.mChanges.add(mChanges.get(i).localRemoteCopy());
         }
-        out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+        for (int i = 0; i < mRoots.size(); ++i) {
+            out.mRoots.add(mRoots.get(i).localRemoteCopy());
+        }
         // Doesn't have any native stuff, so no need for actual copy
         out.mOptions = mOptions;
-        out.mRootOffset.set(mRootOffset);
         return out;
     }
 
@@ -496,6 +534,8 @@
         private final Point mEndRelOffset = new Point();
         private ActivityManager.RunningTaskInfo mTaskInfo = null;
         private boolean mAllowEnterPip;
+        private int mStartDisplayId = INVALID_DISPLAY;
+        private int mEndDisplayId = INVALID_DISPLAY;
         private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED;
         private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED;
         /**
@@ -526,6 +566,8 @@
             mEndRelOffset.readFromParcel(in);
             mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
             mAllowEnterPip = in.readBoolean();
+            mStartDisplayId = in.readInt();
+            mEndDisplayId = in.readInt();
             mStartRotation = in.readInt();
             mEndRotation = in.readInt();
             mEndFixedRotation = in.readInt();
@@ -546,6 +588,8 @@
             out.mEndRelOffset.set(mEndRelOffset);
             out.mTaskInfo = mTaskInfo;
             out.mAllowEnterPip = mAllowEnterPip;
+            out.mStartDisplayId = mStartDisplayId;
+            out.mEndDisplayId = mEndDisplayId;
             out.mStartRotation = mStartRotation;
             out.mEndRotation = mEndRotation;
             out.mEndFixedRotation = mEndFixedRotation;
@@ -608,6 +652,12 @@
         }
 
         /** Sets the start and end rotation of this container. */
+        public void setDisplayId(int start, int end) {
+            mStartDisplayId = start;
+            mEndDisplayId = end;
+        }
+
+        /** Sets the start and end rotation of this container. */
         public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
             mStartRotation = start;
             mEndRotation = end;
@@ -725,6 +775,14 @@
             return mAllowEnterPip;
         }
 
+        public int getStartDisplayId() {
+            return mStartDisplayId;
+        }
+
+        public int getEndDisplayId() {
+            return mEndDisplayId;
+        }
+
         @Surface.Rotation
         public int getStartRotation() {
             return mStartRotation;
@@ -776,6 +834,8 @@
             mEndRelOffset.writeToParcel(dest, flags);
             dest.writeTypedObject(mTaskInfo, flags);
             dest.writeBoolean(mAllowEnterPip);
+            dest.writeInt(mStartDisplayId);
+            dest.writeInt(mEndDisplayId);
             dest.writeInt(mStartRotation);
             dest.writeInt(mEndRotation);
             dest.writeInt(mEndFixedRotation);
@@ -822,6 +882,11 @@
             if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
                 sb.append(" eo="); sb.append(mEndRelOffset);
             }
+            sb.append(" d=");
+            if (mStartDisplayId != mEndDisplayId) {
+                sb.append(mStartDisplayId).append("->");
+            }
+            sb.append(mEndDisplayId);
             if (mStartRotation != mEndRotation) {
                 sb.append(" r="); sb.append(mStartRotation);
                 sb.append("->"); sb.append(mEndRotation);
@@ -1108,4 +1173,86 @@
                     };
         }
     }
+
+    /**
+     * An animation root in a transition. There is one of these for each display that contains
+     * participants. It will be placed, in z-order, right above the top-most participant and at the
+     * same position in the hierarchy. As a result, if all participants are animating within a
+     * part of the screen, the root-leash will only be in that part of the screen. In these cases,
+     * it's relative position (from the screen) is stored in {@link Root#getOffset}.
+     */
+    public static final class Root implements Parcelable {
+        private final int mDisplayId;
+        private final SurfaceControl mLeash;
+        private final Point mOffset = new Point();
+
+        public Root(int displayId, @NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
+            mDisplayId = displayId;
+            mLeash = leash;
+            mOffset.set(offsetLeft, offsetTop);
+        }
+
+        private Root(Parcel in) {
+            mDisplayId = in.readInt();
+            mLeash = new SurfaceControl();
+            mLeash.readFromParcel(in);
+            mLeash.setUnreleasedWarningCallSite("TransitionInfo.Root");
+            mOffset.readFromParcel(in);
+        }
+
+        private Root localRemoteCopy() {
+            return new Root(mDisplayId, new SurfaceControl(mLeash, "localRemote"),
+                    mOffset.x, mOffset.y);
+        }
+
+        /** @return the id of the display this root is on. */
+        public int getDisplayId() {
+            return mDisplayId;
+        }
+
+        /** @return the root's leash. Surfaces should be parented to this while animating. */
+        @NonNull
+        public SurfaceControl getLeash() {
+            return mLeash;
+        }
+
+        /** @return the offset (relative to its screen) of the root leash. */
+        @NonNull
+        public Point getOffset() {
+            return mOffset;
+        }
+
+        /** @hide */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mDisplayId);
+            mLeash.writeToParcel(dest, flags);
+            mOffset.writeToParcel(dest, flags);
+        }
+
+        @NonNull
+        public static final Creator<Root> CREATOR =
+                new Creator<Root>() {
+                    @Override
+                    public Root createFromParcel(Parcel in) {
+                        return new Root(in);
+                    }
+
+                    @Override
+                    public Root[] newArray(int size) {
+                        return new Root[size];
+                    }
+                };
+
+        /** @hide */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return mDisplayId + "@" + mOffset + ":" + mLeash;
+        }
+    }
 }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index a3209f6..fabb089 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -131,6 +131,19 @@
     }
 
     /**
+     * Sets the densityDpi value in the configuration for the given container.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setDensityDpi(@NonNull WindowContainerToken container,
+            int densityDpi) {
+        Change chg = getOrCreateChange(container.asBinder());
+        chg.mConfiguration.densityDpi = densityDpi;
+        chg.mConfigSetMask |= ActivityInfo.CONFIG_DENSITY;
+        return this;
+    }
+
+    /**
      * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
      * has finished the enter animation with the given bounds.
      */
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 429156f..01e577f 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -63,10 +63,17 @@
         @NonNull
         public final Rect bounds;
 
-        WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) {
+        /**
+         * True if the window is a trusted overlay.
+         */
+        public final boolean isTrustedOverlay;
+
+        WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds,
+                int inputConfig) {
             this.windowToken = windowToken;
             this.name = name;
             this.bounds = bounds;
+            this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
         }
     }
 
@@ -129,7 +136,8 @@
             }
             var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
                     handle.frameBottom);
-            windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds));
+            windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds,
+                    handle.inputConfig));
         }
         return windowInfos;
     }
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 57bd3f9..d521866 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -20,6 +20,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.icu.text.CaseMap;
 import android.icu.text.ListFormatter;
+import android.icu.text.NumberingSystem;
 import android.icu.util.ULocale;
 import android.os.LocaleList;
 import android.text.TextUtils;
@@ -173,6 +174,21 @@
     }
 
     /**
+     * Returns numbering system value of a locale for display in the provided locale.
+     *
+     * @param locale The locale whose key value is displayed.
+     * @param displayLocale The locale in which to display the key value.
+     * @return The string of numbering system.
+     */
+    public static String getDisplayNumberingSystemKeyValue(
+            Locale locale, Locale displayLocale) {
+        ULocale uLocale = new ULocale.Builder()
+                .setUnicodeLocaleKeyword("nu", NumberingSystem.getInstance(locale).getName())
+                .build();
+        return uLocale.getDisplayKeywordValue("numbers", ULocale.forLocale(displayLocale));
+    }
+
+    /**
      * Adds the likely subtags for a provided locale ID.
      *
      * @param locale the locale to maximize.
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 685bd9a..5dfc0ea 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -61,6 +61,7 @@
     private int mTopDistance = 0;
     private CharSequence mTitle = null;
     private OnActionExpandListener mOnActionExpandListener;
+    private boolean mIsNumberingSystem = false;
 
     /**
      * Other classes can register to be notified when a locale was selected.
@@ -90,6 +91,18 @@
         boolean hasSpecificPackageName();
     }
 
+    private static LocalePickerWithRegion createNumberingSystemPicker(
+            LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+            LocaleCollectorBase localePickerCollector) {
+        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
+        localePicker.setOnActionExpandListener(onActionExpandListener);
+        localePicker.setIsNumberingSystem(true);
+        boolean shouldShowTheList = localePicker.setListener(listener, parent,
+                translatedOnly, localePickerCollector);
+        return shouldShowTheList ? localePicker : null;
+    }
+
     private static LocalePickerWithRegion createCountryPicker(
             LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
             boolean translatedOnly, OnActionExpandListener onActionExpandListener,
@@ -128,6 +141,10 @@
         return localePicker;
     }
 
+    private void setIsNumberingSystem(boolean isNumberingSystem) {
+        mIsNumberingSystem = isNumberingSystem;
+    }
+
     /**
      * Sets the listener and initializes the locale list.
      *
@@ -184,6 +201,7 @@
         final boolean hasSpecificPackageName =
                 mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
         mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
+        mAdapter.setNumberingSystemMode(mIsNumberingSystem);
         final LocaleHelper.LocaleInfoComparator comp =
                 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
         mAdapter.sort(comp);
@@ -213,7 +231,6 @@
     @Override
     public void onResume() {
         super.onResume();
-
         if (mParentLocale != null) {
             getActivity().setTitle(mParentLocale.getFullNameNative());
         } else {
@@ -250,16 +267,28 @@
         // Special case for resetting the app locale to equal the system locale.
         boolean isSystemLocale = locale.isSystemLocale();
         boolean isRegionLocale = locale.getParent() != null;
+        boolean mayHaveDifferentNumberingSystem = locale.hasNumberingSystems();
 
-        if (isSystemLocale || isRegionLocale) {
+        if (isSystemLocale
+                || (isRegionLocale && !mayHaveDifferentNumberingSystem)
+                || mIsNumberingSystem) {
             if (mListener != null) {
                 mListener.onLocaleSelected(locale);
             }
             returnToParentFrame();
         } else {
-            LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
-                    mListener, locale, mTranslatedOnly /* translate only */,
-                    mOnActionExpandListener, this.mLocalePickerCollector);
+            LocalePickerWithRegion selector;
+            if (mayHaveDifferentNumberingSystem) {
+                selector =
+                        LocalePickerWithRegion.createNumberingSystemPicker(
+                        mListener, locale, mTranslatedOnly /* translate only */,
+                        mOnActionExpandListener, this.mLocalePickerCollector);
+            } else {
+                selector = LocalePickerWithRegion.createCountryPicker(
+                        mListener, locale, mTranslatedOnly /* translate only */,
+                        mOnActionExpandListener, this.mLocalePickerCollector);
+            }
+
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 8b41829..bcbfdc9 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -39,6 +39,9 @@
 import java.util.Set;
 
 public class LocaleStore {
+    private static final int TIER_LANGUAGE = 1;
+    private static final int TIER_REGION = 2;
+    private static final int TIER_NUMBERING = 3;
     private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>();
     private static final String TAG = LocaleStore.class.getSimpleName();
     private static boolean sFullyInitialized = false;
@@ -68,10 +71,13 @@
         private String mFullCountryNameNative;
         private String mLangScriptKey;
 
+        private boolean mHasNumberingSystems;
+
         private LocaleInfo(Locale locale) {
             this.mLocale = locale;
             this.mId = locale.toLanguageTag();
             this.mParent = getParent(locale);
+            this.mHasNumberingSystems = false;
             this.mIsChecked = false;
             this.mSuggestionFlags = SUGGESTION_TYPE_NONE;
             this.mIsTranslated = false;
@@ -93,6 +99,11 @@
                     .build();
         }
 
+        /** Return true if there are any same locales with different numbering system. */
+        public boolean hasNumberingSystems() {
+            return mHasNumberingSystems;
+        }
+
         @Override
         public String toString() {
             return mId;
@@ -195,6 +206,10 @@
             }
         }
 
+        String getNumberingSystem() {
+            return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale);
+        }
+
         String getContentDescription(boolean countryMode) {
             if (countryMode) {
                 return getFullCountryNameInUiLanguage();
@@ -383,6 +398,12 @@
 
         final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+        Set<Locale> numberSystemLocaleList = new HashSet<>();
+        for (String localeId : LocalePicker.getSupportedLocales(context)) {
+            if (Locale.forLanguageTag(localeId).getUnicodeLocaleType("nu") != null) {
+                numberSystemLocaleList.add(Locale.forLanguageTag(localeId));
+            }
+        }
         for (String localeId : LocalePicker.getSupportedLocales(context)) {
             if (localeId.isEmpty()) {
                 throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
@@ -403,6 +424,12 @@
             if (simCountries.contains(li.getLocale().getCountry())) {
                 li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
             }
+            numberSystemLocaleList.forEach(l -> {
+                if (li.getLocale().stripExtensions().equals(l.stripExtensions())) {
+                    li.mHasNumberingSystems = true;
+                }
+            });
+
             sLocaleCache.put(li.getId(), li);
             final Locale parent = li.getParent();
             if (parent != null) {
@@ -445,20 +472,43 @@
         sFullyInitialized = true;
     }
 
-    private static int getLevel(Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
-        if (ignorables.contains(li.getId())) return 0;
-        if (li.mIsPseudo) return 2;
-        if (translatedOnly && !li.isTranslated()) return 0;
-        if (li.getParent() != null) return 2;
-        return 0;
+    private static boolean isShallIgnore(
+            Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
+        if (ignorables.stream().anyMatch(tag ->
+                Locale.forLanguageTag(tag).stripExtensions()
+                        .equals(li.getLocale().stripExtensions()))) {
+            return true;
+        }
+        if (li.mIsPseudo) return false;
+        if (translatedOnly && !li.isTranslated()) return true;
+        if (li.getParent() != null) return false;
+        return true;
+    }
+
+    private static int getLocaleTier(LocaleInfo parent) {
+        if (parent == null) {
+            return TIER_LANGUAGE;
+        } else if (parent.getLocale().getCountry().isEmpty()) {
+            return TIER_REGION;
+        } else {
+            return TIER_NUMBERING;
+        }
     }
 
     /**
      * Returns a list of locales for language or region selection.
+     *
      * If the parent is null, then it is the language list.
+     *
      * If it is not null, then the list will contain all the locales that belong to that parent.
      * Example: if the parent is "ar", then the region list will contain all Arabic locales.
-     * (this is not language based, but language-script, so that it works for zh-Hant and so on.
+     * (this is not language based, but language-script, so that it works for zh-Hant and so on.)
+     *
+     * If it is not null and has country, then the list will contain all locales with that parent's
+     * language and country, i.e. containing alternate numbering systems.
+     *
+     * Example: if the parent is "ff-Adlm-BF", then the numbering list will contain all
+     * Fula (Adlam, Burkina Faso) i.e. "ff-Adlm-BF" and "ff-Adlm-BF-u-nu-latn"
      */
     @UnsupportedAppUsage
     public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
@@ -478,28 +528,49 @@
      */
     public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
             LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
-        fillCache(context);
-        String parentId = parent == null ? null : parent.getId();
-        HashSet<LocaleInfo> result = new HashSet<>();
+        if (context != null) {
+            fillCache(context);
+        }
         HashMap<String, LocaleInfo> supportedLcoaleInfos =
                 explicitLocales == null
                         ? sLocaleCache
                         : convertExplicitLocales(explicitLocales, sLocaleCache.values());
+        return getTierLocales(ignorables, parent, translatedOnly, supportedLcoaleInfos);
+    }
 
+    private static Set<LocaleInfo> getTierLocales(
+            Set<String> ignorables,
+            LocaleInfo parent,
+            boolean translatedOnly,
+            HashMap<String, LocaleInfo> supportedLcoaleInfos) {
+
+        boolean hasTargetParent = parent != null;
+        String parentId = hasTargetParent ? parent.getId() : null;
+        HashSet<LocaleInfo> result = new HashSet<>();
         for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
-            int level = getLevel(ignorables, li, translatedOnly);
-            if (level == 2) {
-                if (parent != null) { // region selection
-                    if (parentId.equals(li.getParent().toLanguageTag())) {
-                        result.add(li);
-                    }
-                } else { // language selection
+            if (isShallIgnore(ignorables, li, translatedOnly)) {
+                continue;
+            }
+            switch(getLocaleTier(parent)) {
+                case TIER_LANGUAGE:
                     if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
                         result.add(li);
                     } else {
-                        result.add(getLocaleInfo(li.getParent()));
+                        result.add(getLocaleInfo(li.getParent(), supportedLcoaleInfos));
                     }
-                }
+                    break;
+                case TIER_REGION:
+                    if (parentId.equals(li.getParent().toLanguageTag())) {
+                        result.add(getLocaleInfo(
+                                li.getLocale().stripExtensions(), supportedLcoaleInfos));
+                    }
+                    break;
+                case TIER_NUMBERING:
+                    if (parent.getLocale().stripExtensions()
+                            .equals(li.getLocale().stripExtensions())) {
+                        result.add(li);
+                    }
+                    break;
             }
         }
         return result;
@@ -538,18 +609,21 @@
     }
 
     private static LocaleList matchLocaleFromSupportedLocaleList(
-            LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+            LocaleList explicitLocales, Collection<LocaleInfo> localeInfos) {
+        if (localeInfos == null) {
+            return explicitLocales;
+        }
         //TODO: Adds a function for unicode extension if needed.
         Locale[] resultLocales = new Locale[explicitLocales.size()];
         for (int i = 0; i < explicitLocales.size(); i++) {
-            Locale locale = explicitLocales.get(i).stripExtensions();
+            Locale locale = explicitLocales.get(i);
             if (!TextUtils.isEmpty(locale.getCountry())) {
-                for (LocaleInfo localeInfo :localeinfo) {
+                for (LocaleInfo localeInfo :localeInfos) {
                     if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
                             && TextUtils.equals(locale.getCountry(),
                             localeInfo.getLocale().getCountry())) {
                         resultLocales[i] = localeInfo.getLocale();
-                        continue;
+                        break;
                     }
                 }
             }
@@ -562,18 +636,23 @@
 
     @UnsupportedAppUsage
     public static LocaleInfo getLocaleInfo(Locale locale) {
+        return getLocaleInfo(locale, sLocaleCache);
+    }
+
+    private static LocaleInfo getLocaleInfo(
+            Locale locale, HashMap<String, LocaleInfo> localeInfos) {
         String id = locale.toLanguageTag();
         LocaleInfo result;
-        if (!sLocaleCache.containsKey(id)) {
+        if (!localeInfos.containsKey(id)) {
             // Locale preferences can modify the language tag to current system languages, so we
             // need to check the input locale without extra u extension except numbering system.
             Locale filteredLocale = new Locale.Builder()
                     .setLocale(locale.stripExtensions())
                     .setUnicodeLocaleKeyword("nu", locale.getUnicodeLocaleType("nu"))
                     .build();
-            if (sLocaleCache.containsKey(filteredLocale.toLanguageTag())) {
+            if (localeInfos.containsKey(filteredLocale.toLanguageTag())) {
                 result = new LocaleInfo(locale);
-                LocaleInfo localeInfo = sLocaleCache.get(filteredLocale.toLanguageTag());
+                LocaleInfo localeInfo = localeInfos.get(filteredLocale.toLanguageTag());
                 // This locale is included in supported locales, so follow the settings
                 // of supported locales.
                 result.mIsPseudo = localeInfo.mIsPseudo;
@@ -582,9 +661,9 @@
                 return result;
             }
             result = new LocaleInfo(locale);
-            sLocaleCache.put(id, result);
+            localeInfos.put(id, result);
         } else {
-            result = sLocaleCache.get(id);
+            result = localeInfos.get(id);
         }
         return result;
     }
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index a61a6d7..08de4dfb 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -64,6 +64,7 @@
     protected ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
     protected int mSuggestionCount;
     protected final boolean mCountryMode;
+    protected boolean mIsNumberingMode;
     protected LayoutInflater mInflater;
 
     protected Locale mDisplayLocale = null;
@@ -89,6 +90,14 @@
         }
     }
 
+    public void setNumberingSystemMode(boolean isNumberSystemMode) {
+        mIsNumberingMode = isNumberSystemMode;
+    }
+
+    public boolean getIsForNumberingSystem() {
+        return mIsNumberingMode;
+    }
+
     @Override
     public boolean areAllItemsEnabled() {
         return false;
@@ -209,7 +218,6 @@
         if (convertView == null && mInflater == null) {
             mInflater = LayoutInflater.from(parent.getContext());
         }
-
         int itemType = getItemViewType(position);
         View itemView = getNewViewIfNeeded(convertView, parent, itemType, position);
         switch (itemType) {
@@ -217,13 +225,13 @@
             case TYPE_HEADER_ALL_OTHERS:
                 TextView textView = (TextView) itemView;
                 if (itemType == TYPE_HEADER_SUGGESTED) {
-                   if (mCountryMode) {
+                    if (mCountryMode && !mIsNumberingMode) {
                         setTextTo(textView, R.string.language_picker_regions_section_suggested);
                     } else {
                         setTextTo(textView, R.string.language_picker_section_suggested);
                     }
                 } else {
-                    if (mCountryMode) {
+                    if (mCountryMode && !mIsNumberingMode) {
                         setTextTo(textView, R.string.region_picker_section_all);
                     } else {
                         setTextTo(textView, R.string.language_picker_section_all);
@@ -419,9 +427,11 @@
 
     private void updateTextView(View convertView, TextView text, int position) {
         LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
-        text.setText(item.getLabel(mCountryMode));
+        text.setText(mIsNumberingMode
+                ? item.getNumberingSystem() : item.getLabel(mCountryMode));
         text.setTextLocale(item.getLocale());
-        text.setContentDescription(item.getContentDescription(mCountryMode));
+        text.setContentDescription(mIsNumberingMode
+                        ? item.getNumberingSystem() : item.getContentDescription(mCountryMode));
         if (mCountryMode) {
             int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
             //noinspection ResourceType
diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java
index bce0d60..f6bcc46 100644
--- a/core/java/com/android/internal/app/procstats/DumpUtils.java
+++ b/core/java/com/android/internal/app/procstats/DumpUtils.java
@@ -27,12 +27,12 @@
 import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_OFF;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_ON;
 import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
 import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
 import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -72,7 +72,8 @@
         STATE_NAMES = new String[STATE_COUNT];
         STATE_NAMES[STATE_PERSISTENT]               = "Persist";
         STATE_NAMES[STATE_TOP]                      = "Top";
-        STATE_NAMES[STATE_BOUND_TOP_OR_FGS]         = "BTopFgs";
+        STATE_NAMES[STATE_BOUND_FGS]                = "BFgs";
+        STATE_NAMES[STATE_BOUND_TOP]                = "BTop";
         STATE_NAMES[STATE_FGS]                      = "Fgs";
         STATE_NAMES[STATE_IMPORTANT_FOREGROUND]     = "ImpFg";
         STATE_NAMES[STATE_IMPORTANT_BACKGROUND]     = "ImpBg";
@@ -83,14 +84,14 @@
         STATE_NAMES[STATE_HEAVY_WEIGHT]             = "HeavyWt";
         STATE_NAMES[STATE_HOME]                     = "Home";
         STATE_NAMES[STATE_LAST_ACTIVITY]            = "LastAct";
-        STATE_NAMES[STATE_CACHED_ACTIVITY]          = "CchAct";
-        STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT]   = "CchCAct";
-        STATE_NAMES[STATE_CACHED_EMPTY]             = "CchEmty";
+        STATE_NAMES[STATE_CACHED]                   = "Cached";
+        STATE_NAMES[STATE_FROZEN]                   = "Frozen";
 
         STATE_LABELS = new String[STATE_COUNT];
         STATE_LABELS[STATE_PERSISTENT]              = "Persistent";
         STATE_LABELS[STATE_TOP]                     = "       Top";
-        STATE_LABELS[STATE_BOUND_TOP_OR_FGS]        = "Bnd TopFgs";
+        STATE_LABELS[STATE_BOUND_FGS]               = "   Bnd Fgs";
+        STATE_LABELS[STATE_BOUND_TOP]               = "   Bnd Top";
         STATE_LABELS[STATE_FGS]                     = "       Fgs";
         STATE_LABELS[STATE_IMPORTANT_FOREGROUND]    = "    Imp Fg";
         STATE_LABELS[STATE_IMPORTANT_BACKGROUND]    = "    Imp Bg";
@@ -101,16 +102,16 @@
         STATE_LABELS[STATE_HEAVY_WEIGHT]            = " Heavy Wgt";
         STATE_LABELS[STATE_HOME]                    = "    (Home)";
         STATE_LABELS[STATE_LAST_ACTIVITY]           = "(Last Act)";
-        STATE_LABELS[STATE_CACHED_ACTIVITY]         = " (Cch Act)";
-        STATE_LABELS[STATE_CACHED_ACTIVITY_CLIENT]  = "(Cch CAct)";
-        STATE_LABELS[STATE_CACHED_EMPTY]            = "(Cch Emty)";
+        STATE_LABELS[STATE_CACHED]                  = "  (Cached)";
+        STATE_LABELS[STATE_FROZEN]                  = "    Frozen";
         STATE_LABEL_CACHED                          = "  (Cached)";
         STATE_LABEL_TOTAL                           = "     TOTAL";
 
         STATE_NAMES_CSV = new String[STATE_COUNT];
         STATE_NAMES_CSV[STATE_PERSISTENT]               = "pers";
         STATE_NAMES_CSV[STATE_TOP]                      = "top";
-        STATE_NAMES_CSV[STATE_BOUND_TOP_OR_FGS]         = "btopfgs";
+        STATE_NAMES_CSV[STATE_BOUND_FGS]                = "bfgs";
+        STATE_NAMES_CSV[STATE_BOUND_TOP]                = "btop";
         STATE_NAMES_CSV[STATE_FGS]                      = "fgs";
         STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND]     = "impfg";
         STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND]     = "impbg";
@@ -121,14 +122,14 @@
         STATE_NAMES_CSV[STATE_HEAVY_WEIGHT]             = "heavy";
         STATE_NAMES_CSV[STATE_HOME]                     = "home";
         STATE_NAMES_CSV[STATE_LAST_ACTIVITY]            = "lastact";
-        STATE_NAMES_CSV[STATE_CACHED_ACTIVITY]          = "cch-activity";
-        STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT]   = "cch-aclient";
-        STATE_NAMES_CSV[STATE_CACHED_EMPTY]             = "cch-empty";
+        STATE_NAMES_CSV[STATE_CACHED]                   = "cached";
+        STATE_NAMES_CSV[STATE_FROZEN]                   = "frzn";
 
         STATE_TAGS = new String[STATE_COUNT];
         STATE_TAGS[STATE_PERSISTENT]                = "p";
         STATE_TAGS[STATE_TOP]                       = "t";
-        STATE_TAGS[STATE_BOUND_TOP_OR_FGS]          = "d";
+        STATE_TAGS[STATE_BOUND_FGS]                 = "y";
+        STATE_TAGS[STATE_BOUND_TOP]                 = "z";
         STATE_TAGS[STATE_FGS]                       = "g";
         STATE_TAGS[STATE_IMPORTANT_FOREGROUND]      = "f";
         STATE_TAGS[STATE_IMPORTANT_BACKGROUND]      = "b";
@@ -139,15 +140,14 @@
         STATE_TAGS[STATE_HEAVY_WEIGHT]              = "w";
         STATE_TAGS[STATE_HOME]                      = "h";
         STATE_TAGS[STATE_LAST_ACTIVITY]             = "l";
-        STATE_TAGS[STATE_CACHED_ACTIVITY]           = "a";
-        STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT]    = "c";
-        STATE_TAGS[STATE_CACHED_EMPTY]              = "e";
+        STATE_TAGS[STATE_CACHED]                    = "a";
+        STATE_TAGS[STATE_FROZEN]                    = "e";
 
         STATE_PROTO_ENUMS = new int[STATE_COUNT];
         STATE_PROTO_ENUMS[STATE_PERSISTENT] = ProcessStatsEnums.PROCESS_STATE_PERSISTENT;
         STATE_PROTO_ENUMS[STATE_TOP] = ProcessStatsEnums.PROCESS_STATE_TOP;
-        STATE_PROTO_ENUMS[STATE_BOUND_TOP_OR_FGS] =
-                ProcessStatsEnums.PROCESS_STATE_BOUND_TOP_OR_FGS;
+        STATE_PROTO_ENUMS[STATE_BOUND_FGS] = ProcessStatsEnums.PROCESS_STATE_BOUND_FGS;
+        STATE_PROTO_ENUMS[STATE_BOUND_TOP] = ProcessStatsEnums.PROCESS_STATE_BOUND_TOP;
         STATE_PROTO_ENUMS[STATE_FGS] = ProcessStatsEnums.PROCESS_STATE_FGS;
         STATE_PROTO_ENUMS[STATE_IMPORTANT_FOREGROUND] =
                 ProcessStatsEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
@@ -161,10 +161,8 @@
         STATE_PROTO_ENUMS[STATE_HEAVY_WEIGHT] = ProcessStatsEnums.PROCESS_STATE_HEAVY_WEIGHT;
         STATE_PROTO_ENUMS[STATE_HOME] = ProcessStatsEnums.PROCESS_STATE_HOME;
         STATE_PROTO_ENUMS[STATE_LAST_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_LAST_ACTIVITY;
-        STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
-        STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY_CLIENT] =
-                ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
-        STATE_PROTO_ENUMS[STATE_CACHED_EMPTY] = ProcessStatsEnums.PROCESS_STATE_CACHED_EMPTY;
+        STATE_PROTO_ENUMS[STATE_CACHED] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
+        STATE_PROTO_ENUMS[STATE_FROZEN] = ProcessStatsEnums.PROCESS_STATE_FROZEN;
 
         // Remap states, as defined by ProcessStats.java, to a reduced subset of states for data
         // aggregation / size reduction purposes.
@@ -173,7 +171,9 @@
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_PERSISTENT;
         PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_TOP] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_TOP;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP_OR_FGS] =
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_FGS] =
+                ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
         PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FGS] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_FGS;
@@ -196,11 +196,9 @@
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
         PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_LAST_ACTIVITY] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY] =
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY_CLIENT] =
-                ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_EMPTY] =
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FROZEN] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
     }
 
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 818a503..fff778c 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -28,10 +28,9 @@
 import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM;
 import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM;
 import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
 import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
@@ -85,9 +84,9 @@
         STATE_PERSISTENT,               // ActivityManager.PROCESS_STATE_PERSISTENT
         STATE_PERSISTENT,               // ActivityManager.PROCESS_STATE_PERSISTENT_UI
         STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP
-        STATE_BOUND_TOP_OR_FGS,         // ActivityManager.PROCESS_STATE_BOUND_TOP
+        STATE_BOUND_TOP,                // ActivityManager.PROCESS_STATE_BOUND_TOP
         STATE_FGS,                      // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        STATE_BOUND_TOP_OR_FGS,         // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+        STATE_BOUND_FGS,                // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
@@ -98,10 +97,10 @@
         STATE_HEAVY_WEIGHT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         STATE_HOME,                     // ActivityManager.PROCESS_STATE_HOME
         STATE_LAST_ACTIVITY,            // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
-        STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
-        STATE_CACHED_ACTIVITY_CLIENT,   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
-        STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_RECENT
-        STATE_CACHED_EMPTY,             // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_RECENT
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
     public static final Comparator<ProcessState> COMPARATOR = new Comparator<ProcessState>() {
@@ -926,8 +925,11 @@
                 screenStates, memStates, new int[] { STATE_PERSISTENT }, now, totalTime, true);
         dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_TOP],
                 screenStates, memStates, new int[] {STATE_TOP}, now, totalTime, true);
-        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP_OR_FGS],
-                screenStates, memStates, new int[] { STATE_BOUND_TOP_OR_FGS}, now, totalTime,
+        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP],
+                screenStates, memStates, new int[] { STATE_BOUND_TOP }, now, totalTime,
+                true);
+        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_FGS],
+                screenStates, memStates, new int[] { STATE_BOUND_FGS }, now, totalTime,
                 true);
         dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_FGS],
                 screenStates, memStates, new int[] { STATE_FGS}, now, totalTime,
@@ -953,9 +955,6 @@
                 screenStates, memStates, new int[] {STATE_HOME}, now, totalTime, true);
         dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_LAST_ACTIVITY],
                 screenStates, memStates, new int[] {STATE_LAST_ACTIVITY}, now, totalTime, true);
-        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABEL_CACHED,
-                screenStates, memStates, new int[] {STATE_CACHED_ACTIVITY,
-                        STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY}, now, totalTime, true);
     }
 
     public void dumpProcessState(PrintWriter pw, String prefix,
@@ -1563,7 +1562,10 @@
                 case STATE_TOP:
                     topMs += duration;
                     break;
-                case STATE_BOUND_TOP_OR_FGS:
+                case STATE_BOUND_FGS:
+                    boundFgsMs += duration;
+                    break;
+                case STATE_BOUND_TOP:
                     boundTopMs += duration;
                     break;
                 case STATE_FGS:
@@ -1583,13 +1585,10 @@
                 case STATE_PERSISTENT:
                     otherMs += duration;
                     break;
-                case STATE_CACHED_ACTIVITY:
-                case STATE_CACHED_ACTIVITY_CLIENT:
-                case STATE_CACHED_EMPTY:
+                case STATE_CACHED:
                     cachedMs += duration;
                     break;
-                    // TODO (b/261910877) Add support for tracking boundFgsMs and
-                    // frozenMs.
+                    // TODO (b/261910877) Add support for tracking frozenMs.
             }
         }
         statsEventOutput.write(
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index f3ed09a..3ce234b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -81,21 +81,21 @@
     public static final int STATE_NOTHING = -1;
     public static final int STATE_PERSISTENT = 0;
     public static final int STATE_TOP = 1;
-    public static final int STATE_BOUND_TOP_OR_FGS = 2;
+    public static final int STATE_BOUND_TOP = 2;
     public static final int STATE_FGS = 3;
-    public static final int STATE_IMPORTANT_FOREGROUND = 4;
-    public static final int STATE_IMPORTANT_BACKGROUND = 5;
-    public static final int STATE_BACKUP = 6;
-    public static final int STATE_SERVICE = 7;
-    public static final int STATE_SERVICE_RESTARTING = 8;
-    public static final int STATE_RECEIVER = 9;
-    public static final int STATE_HEAVY_WEIGHT = 10;
-    public static final int STATE_HOME = 11;
-    public static final int STATE_LAST_ACTIVITY = 12;
-    public static final int STATE_CACHED_ACTIVITY = 13;
-    public static final int STATE_CACHED_ACTIVITY_CLIENT = 14;
-    public static final int STATE_CACHED_EMPTY = 15;
-    public static final int STATE_COUNT = STATE_CACHED_EMPTY+1;
+    public static final int STATE_BOUND_FGS = 4;
+    public static final int STATE_IMPORTANT_FOREGROUND = 5;
+    public static final int STATE_IMPORTANT_BACKGROUND = 6;
+    public static final int STATE_BACKUP = 7;
+    public static final int STATE_SERVICE = 8;
+    public static final int STATE_SERVICE_RESTARTING = 9;
+    public static final int STATE_RECEIVER = 10;
+    public static final int STATE_HEAVY_WEIGHT = 11;
+    public static final int STATE_HOME = 12;
+    public static final int STATE_LAST_ACTIVITY = 13;
+    public static final int STATE_CACHED = 14;
+    public static final int STATE_FROZEN = 15;
+    public static final int STATE_COUNT = STATE_FROZEN + 1;
 
     public static final int PSS_SAMPLE_COUNT = 0;
     public static final int PSS_MINIMUM = 1;
@@ -154,9 +154,10 @@
     public static final int[] ALL_SCREEN_ADJ = new int[] { ADJ_SCREEN_OFF, ADJ_SCREEN_ON };
 
     public static final int[] NON_CACHED_PROC_STATES = new int[] {
-            STATE_PERSISTENT, STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS,
+            STATE_PERSISTENT, STATE_TOP, STATE_FGS,
             STATE_IMPORTANT_FOREGROUND, STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
-            STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT
+            STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT,
+            STATE_BOUND_TOP, STATE_BOUND_FGS
     };
 
     public static final int[] BACKGROUND_PROC_STATES = new int[] {
@@ -165,11 +166,11 @@
     };
 
     public static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT,
-            STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
+            STATE_TOP, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
             STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
             STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
-            STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
-            STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY
+            STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED,
+            STATE_BOUND_TOP, STATE_BOUND_FGS, STATE_FROZEN
     };
 
     // Should report process stats.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 496cc3a..479ea4e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3161,7 +3161,7 @@
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
          device. -->
     <permission android:name="android.permission.QUERY_USERS"
-                android:protectionLevel="signature|role" />
+                android:protectionLevel="signature|privileged|role" />
 
     <!-- Allows an application to access data blobs across users. -->
     <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
@@ -4492,7 +4492,7 @@
 
     <!-- Allows an application to be able to store and retrieve credentials from a remote
          device.
-         @hide @SystemApi -->
+    <p>Protection level: signature|privileged|role -->
     <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
                 android:protectionLevel="signature|privileged|role" />
 
diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index d07ad89..1ad3acd 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -56,6 +56,7 @@
             android:paddingTop="16dp"
             android:layout_below="@id/icon"
             android:layout_centerHorizontal="true"
+            android:fontFamily="@string/config_headlineFontFamily"
             android:textSize="24sp"
             android:lineHeight="32sp"
             android:gravity="center"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3074906..d8e69d7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -327,6 +327,7 @@
         <item>@string/wfcSpnFormat_wifi_calling_wo_hyphen</item>
         <item>@string/wfcSpnFormat_vowifi</item>
         <item>@string/wfcSpnFormat_spn_wifi_calling_vo_hyphen</item>
+        <item>@string/wfcSpnFormat_wifi_call</item>
     </string-array>
 
     <!-- Spn during Wi-Fi Calling: "<operator>" -->
@@ -353,6 +354,8 @@
     <string name="wfcSpnFormat_wifi_calling_wo_hyphen">WiFi Calling</string>
     <!-- Spn during Wi-Fi Calling: "VoWifi" -->
     <string name="wfcSpnFormat_vowifi">VoWifi</string>
+    <!-- Spn during Wi_Fi Calling: "WiFi Call" (without hyphen).  This string is shown in the call banner for calls which take place over a WiFi network. -->
+    <string name="wfcSpnFormat_wifi_call">WiFi Call</string>
 
     <!-- WFC, summary for Disabled -->
     <string name="wifi_calling_off_summary">Off</string>
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 1ec2613..fccb177 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -24,6 +24,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -224,7 +225,7 @@
     }
 
     @Test
-    public void onTouchEvent_startHandwriting_delegate() {
+    public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() {
         View delegateView = new View(mContext);
         delegateView.setIsHandwritingDelegate(true);
 
@@ -245,6 +246,29 @@
     }
 
     @Test
+    public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() {
+        View delegateView = new View(mContext);
+        delegateView.setIsHandwritingDelegate(true);
+        mHandwritingInitiator.onInputConnectionCreated(delegateView);
+        reset(mHandwritingInitiator);
+
+        mTestView1.setHandwritingDelegatorCallback(
+                () -> mHandwritingInitiator.onDelegateViewFocused(delegateView));
+
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
+    }
+
+    @Test
     public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() {
         final Rect rect = new Rect(600, 600, 900, 900);
         final View testView = createView(rect, true /* autoHandwritingEnabled */,
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
index 35b3267..6189914 100644
--- a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.MockitoAnnotations.initMocks;
 
+import android.app.ActivityManager;
+
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FrameworkStatsLog;
@@ -128,6 +130,34 @@
     }
 
     @SmallTest
+    public void testDumpBoundFgsDuration() throws Exception {
+        ProcessStats processStats = new ProcessStats();
+        ProcessState processState =
+                processStats.getProcessStateLocked(
+                        APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+        processState.setState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+                ProcessStats.ADJ_MEM_FACTOR_NORMAL, NOW_MS, /* pkgList */ null);
+        processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+        processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+        verify(mStatsEventOutput)
+                .write(
+                        eq(FrameworkStatsLog.PROCESS_STATE),
+                        eq(APP_1_UID),
+                        eq(APP_1_PROCESS_NAME),
+                        anyInt(),
+                        anyInt(),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(DURATION_SECS),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(0));
+    }
+
+    @SmallTest
     public void testDumpProcessAssociation() throws Exception {
         ProcessStats processStats = new ProcessStats();
         AssociationState associationState =
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e287867..5d303cf 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -275,6 +275,7 @@
         <!-- Permission required to test onPermissionsChangedListener -->
         <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.QUERY_USERS"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 8e2a59c..e7ddc88 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -255,9 +255,12 @@
     </family>
 
     <family name="cursive">
-        <font weight="400" style="normal" postScriptName="DancingScript">DancingScript-Regular.ttf
-        </font>
-        <font weight="700" style="normal">DancingScript-Bold.ttf</font>
+      <font weight="400" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="700" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="700" />
+      </font>
     </family>
 
     <family name="sans-serif-smallcaps">
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index fe6eeeb..1048742 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -42,9 +42,10 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
-    alt:                                '\u00e7'
-    shift+alt:                          '\u00c7'
     shift+capslock:                     'c'
+    alt:                                '\u00e7'
+    shift+alt, capslock+alt:            '\u00c7'
+    shift+capslock+alt:                 '\u00e7'
 }
 
 key D {
@@ -58,8 +59,8 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
-    alt:                                '\u0301'
     shift+capslock:                     'e'
+    alt:                                '\u0301'
 }
 
 key F {
@@ -87,8 +88,8 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
-    alt:                                '\u0302'
     shift+capslock:                     'i'
+    alt:                                '\u0302'
 }
 
 key J {
@@ -123,8 +124,8 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
-    alt:                                '\u0303'
     shift+capslock:                     'n'
+    alt:                                '\u0303'
 }
 
 key O {
@@ -159,8 +160,8 @@
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
-    alt:                                '\u00df'
     shift+capslock:                     's'
+    alt:                                '\u00df'
 }
 
 key T {
@@ -174,8 +175,8 @@
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
-    alt:                                '\u0308'
     shift+capslock:                     'u'
+    alt:                                '\u0308'
 }
 
 key V {
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
deleted file mode 120000
index 707dfcf..0000000
--- a/data/keyboards/Vendor_004c_Product_0265.idc
+++ /dev/null
@@ -1 +0,0 @@
-Vendor_05ac_Product_0265.idc
\ No newline at end of file
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
new file mode 100644
index 0000000..bfea4db
--- /dev/null
+++ b/data/keyboards/Vendor_004c_Product_0265.idc
@@ -0,0 +1,34 @@
+# 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.
+
+#
+# Apple Magic Trackpad 2 (Bluetooth) configuration file
+#
+# WHEN MODIFYING, also change the USB file (Vendor_05ac_Product_0265.idc)
+#
+
+gestureProp.Pressure_Calibration_Offset = 30
+gestureProp.Palm_Pressure = 250.0
+gestureProp.Palm_Width = 20.0
+gestureProp.Multiple_Palm_Width = 20.0
+
+# Enable Stationary Wiggle Filter
+gestureProp.Stationary_Wiggle_Filter_Enabled = 1
+gestureProp.Finger_Moving_Energy = 0.0008
+gestureProp.Finger_Moving_Hysteresis = 0.0004
+
+# Avoid accidental scroll/move on finger lift
+gestureProp.Max_Stationary_Move_Speed = 47
+gestureProp.Max_Stationary_Move_Speed_Hysteresis = 1
+gestureProp.Max_Stationary_Move_Suppress_Distance = 0.2
diff --git a/data/keyboards/Vendor_05ac_Product_0265.idc b/data/keyboards/Vendor_05ac_Product_0265.idc
index d72de64..520d188 100644
--- a/data/keyboards/Vendor_05ac_Product_0265.idc
+++ b/data/keyboards/Vendor_05ac_Product_0265.idc
@@ -13,9 +13,9 @@
 # limitations under the License.
 
 #
-# Apple Magic Trackpad 2 configuration file
-#   Bluetooth vendor ID 004c
-#   USB vendor ID 05ac
+# Apple Magic Trackpad 2 (USB) configuration file
+#
+# WHEN MODIFYING, also change the Bluetooth file (Vendor_004c_Product_0265.idc)
 #
 
 gestureProp.Pressure_Calibration_Offset = 30
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index 53308e3..06b8237 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -39,9 +39,10 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
-    alt:                                '\u00e7'
-    shift+alt:                          '\u00c7'
     shift+capslock:                     'c'
+    alt:                                '\u00e7'
+    shift+alt, capslock+alt:            '\u00c7'
+    shift+capslock+alt:                 '\u00e7'
 }
 
 key D {
@@ -55,8 +56,8 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
-    alt:                                '\u0301'
     shift+capslock:                     'e'
+    alt:                                '\u0301'
 }
 
 key F {
@@ -84,8 +85,8 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
-    alt:                                '\u0302'
     shift+capslock:                     'i'
+    alt:                                 '\u0302'
 }
 
 key J {
@@ -120,8 +121,8 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
-    alt:                                '\u0303'
     shift+capslock:                     'n'
+    alt:                                '\u0303'
 }
 
 key O {
@@ -156,8 +157,8 @@
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
-    alt:                                '\u00df'
     shift+capslock:                     's'
+    alt:                                '\u00df'
 }
 
 key T {
@@ -171,8 +172,8 @@
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
-    alt:                                '\u0308'
     shift+capslock:                     'u'
+    alt:                                '\u0308'
 }
 
 key V {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c1f6c29..c3b0f9b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -808,9 +808,12 @@
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
                         platformReportedBrand.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedDevice =
+                        isPropertyEmptyOrUnknown(Build.DEVICE_FOR_ATTESTATION)
+                                ? Build.DEVICE : Build.DEVICE_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
-                        Build.DEVICE.getBytes(StandardCharsets.UTF_8)
+                        platformReportedDevice.getBytes(StandardCharsets.UTF_8)
                 ));
                 final String platformReportedProduct =
                         isPropertyEmptyOrUnknown(Build.PRODUCT_FOR_ATTESTATION)
@@ -819,9 +822,12 @@
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
                         platformReportedProduct.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedManufacturer =
+                        isPropertyEmptyOrUnknown(Build.MANUFACTURER_FOR_ATTESTATION)
+                                ? Build.MANUFACTURER : Build.MANUFACTURER_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
-                        Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
+                        platformReportedManufacturer.getBytes(StandardCharsets.UTF_8)
                 ));
                 final String platformReportedModel =
                         isPropertyEmptyOrUnknown(Build.MODEL_FOR_ATTESTATION)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index bf226283..cb1a6e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -22,10 +22,12 @@
 import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.view.Surface;
 
@@ -34,6 +36,8 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellInit;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -49,8 +53,34 @@
 public class TabletopModeController implements
         DevicePostureController.OnDevicePostureChangedListener,
         DisplayController.OnDisplaysChangedListener {
+    /**
+     * When {@code true}, floating windows like PiP would auto move to the position
+     * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
+     */
+    private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+            SystemProperties.getBoolean(
+                    "persist.wm.debug.enable_move_floating_window_in_tabletop", false);
+
+    /**
+     * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
+     * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
+     * See also {@link #getPreferredHalfInTabletopMode()}.
+     */
+    private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
+            SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);
+
     private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
 
+    @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
+            PREFERRED_TABLETOP_HALF_TOP,
+            PREFERRED_TABLETOP_HALF_BOTTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PreferredTabletopHalf {}
+
+    public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
+    public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;
+
     private final Context mContext;
 
     private final DevicePostureController mDevicePostureController;
@@ -132,6 +162,22 @@
         }
     }
 
+    /**
+     * @return {@code true} if floating windows like PiP would auto move to the position
+     * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
+     */
+    public boolean enableMoveFloatingWindowInTabletop() {
+        return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
+    }
+
+    /** @return Preferred half for floating windows like PiP when in tabletop mode. */
+    @PreferredTabletopHalf
+    public int getPreferredHalfInTabletopMode() {
+        return PREFER_TOP_HALF_IN_TABLETOP
+                ? PREFERRED_TABLETOP_HALF_TOP
+                : PREFERRED_TABLETOP_HALF_BOTTOM;
+    }
+
     /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
     public void registerOnTabletopModeChangedListener(
             @NonNull OnTabletopModeChangedListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ba0f073..7a83d10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -45,6 +45,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -357,6 +358,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(PipController.create(
@@ -366,7 +368,7 @@
                 pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
                 pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor));
+                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 73a7403..31c5e33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,6 +25,7 @@
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
 import android.os.IBinder
+import android.os.SystemProperties
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
@@ -32,6 +33,7 @@
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.internal.protolog.common.ProtoLog
@@ -115,10 +117,7 @@
         val wct = WindowContainerTransaction()
         // Bring other apps to front first
         bringDesktopAppsToFront(wct)
-
-        wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
-        wct.reorder(task.getToken(), true /* onTop */)
-
+        addMoveToDesktopChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
@@ -136,8 +135,7 @@
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
 
         val wct = WindowContainerTransaction()
-        wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
-        wct.setBounds(task.getToken(), null)
+        addMoveToFullscreenChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
@@ -234,8 +232,8 @@
                         " taskId=%d",
                     task.taskId
                 )
-                return WindowContainerTransaction().apply {
-                    setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+                return WindowContainerTransaction().also { wct ->
+                    addMoveToDesktopChanges(wct, task.token)
                 }
             }
         }
@@ -251,15 +249,44 @@
                         " taskId=%d",
                     task.taskId
                 )
-                return WindowContainerTransaction().apply {
-                    setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
-                    setBounds(task.token, null)
+                return WindowContainerTransaction().also { wct ->
+                    addMoveToFullscreenChanges(wct, task.token)
                 }
             }
         }
         return null
     }
 
+    private fun addMoveToDesktopChanges(
+        wct: WindowContainerTransaction,
+        token: WindowContainerToken
+    ) {
+        wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
+        wct.reorder(token, true /* onTop */)
+        if (isDesktopDensityOverrideSet()) {
+            wct.setDensityDpi(token, getDesktopDensityDpi())
+        }
+    }
+
+    private fun addMoveToFullscreenChanges(
+        wct: WindowContainerTransaction,
+        token: WindowContainerToken
+    ) {
+        wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+        wct.setBounds(token, null)
+        if (isDesktopDensityOverrideSet()) {
+            wct.setDensityDpi(token, getFullscreenDensityDpi())
+        }
+    }
+
+    private fun getFullscreenDensityDpi(): Int {
+        return context.resources.displayMetrics.densityDpi
+    }
+
+    private fun getDesktopDensityDpi(): Int {
+        return DESKTOP_DENSITY_OVERRIDE
+    }
+
     /** Creates a new instance of the external interface to pass to another process. */
     private fun createExternalInterface(): ExternalInterfaceBinder {
         return IDesktopModeImpl(this)
@@ -318,4 +345,18 @@
             return result[0]
         }
     }
+
+    companion object {
+        private val DESKTOP_DENSITY_OVERRIDE =
+            SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+        private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+
+        /**
+         * Check if desktop density override is enabled
+         */
+        @JvmStatic
+        fun isDesktopDensityOverrideSet(): Boolean {
+            return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index f664808..f08742d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -43,7 +43,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -113,6 +115,12 @@
      * @see android.view.View#setPreferKeepClearRects
      */
     private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
+    /**
+     * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
+     * as unrestricted keep clear area. Values in this map would be appended to
+     * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
+     */
+    private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
 
     private @Nullable Runnable mOnMinimalSizeChangeCallback;
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
@@ -378,6 +386,16 @@
         mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
     }
 
+    /** Add a named unrestricted keep clear area. */
+    public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
+        mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+    }
+
+    /** Remove a named unrestricted keep clear area. */
+    public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
+        mNamedUnrestrictedKeepClearAreas.remove(name);
+    }
+
     @NonNull
     public Set<Rect> getRestrictedKeepClearAreas() {
         return mRestrictedKeepClearAreas;
@@ -385,7 +403,10 @@
 
     @NonNull
     public Set<Rect> getUnrestrictedKeepClearAreas() {
-        return mUnrestrictedKeepClearAreas;
+        if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+        final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
+        unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+        return unrestrictedAreas;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 87cf655..45bb73b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -489,10 +489,11 @@
         // Reparent the pip leash to the root with max layer so that we can animate it outside of
         // parent crop, and make sure it is not covered by other windows.
         final SurfaceControl pipLeash = pipChange.getLeash();
-        startTransaction.reparent(pipLeash, info.getRootLeash());
+        final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
+        startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
         startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
         // Note: because of this, the bounds to animate should be translated to the root coordinate.
-        final Point offset = info.getRootOffset();
+        final Point offset = info.getRoot(rootIdx).getOffset();
         final Rect currentBounds = mPipBoundsState.getBounds();
         currentBounds.offset(-offset.x, -offset.y);
         startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index db6ef1d..be9b529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -71,6 +72,7 @@
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
@@ -147,6 +149,7 @@
     private TaskStackListenerImpl mTaskStackListener;
     private PipParamsChangedForwarder mPipParamsChangedForwarder;
     private DisplayInsetsController mDisplayInsetsController;
+    private TabletopModeController mTabletopModeController;
     private Optional<OneHandedController> mOneHandedController;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
@@ -403,6 +406,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor) {
         if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -417,7 +421,7 @@
                 pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
                 pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor)
+                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
                 .mImpl;
     }
 
@@ -444,6 +448,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController tabletopModeController,
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor
     ) {
@@ -477,6 +482,7 @@
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
         mPipParamsChangedForwarder = pipParamsChangedForwarder;
         mDisplayInsetsController = displayInsetsController;
+        mTabletopModeController = tabletopModeController;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -659,6 +665,42 @@
                     }
                 });
 
+        mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
+            if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
+            final String tag = "tabletop-mode";
+            if (!isInTabletopMode) {
+                mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+                return;
+            }
+
+            // To prepare for the entry bounds.
+            final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+            if (mTabletopModeController.getPreferredHalfInTabletopMode()
+                    == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
+                // Prefer top, avoid the bottom half of the display.
+                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+                        displayBounds.left, displayBounds.centerY(),
+                        displayBounds.right, displayBounds.bottom));
+            } else {
+                // Prefer bottom, avoid the top half of the display.
+                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+                        displayBounds.left, displayBounds.top,
+                        displayBounds.right, displayBounds.centerY()));
+            }
+
+            // Try to move the PiP window if we have entered PiP mode.
+            if (mPipTransitionState.hasEnteredPip()) {
+                final Rect pipBounds = mPipBoundsState.getBounds();
+                final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+                if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
+                    // PiP bounds is too big to fit either half, bail early.
+                    return;
+                }
+                mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+                mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
+            }
+        });
+
         mOneHandedController.ifPresent(controller -> {
             controller.registerTransitionCallback(
                     new OneHandedTransitionCallback() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index e1c0895..e09c3c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -37,6 +37,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -128,6 +129,7 @@
             final int mode = info.getChanges().get(i).getMode();
 
             if (mode == TRANSIT_CHANGE) {
+                final int rootIdx = TransitionUtil.rootIndexFor(change, info);
                 if (change.getParent() != null) {
                     // This is probably reparented, so we want the parent to be immediately visible
                     final TransitionInfo.Change parentChange = info.getChange(change.getParent());
@@ -135,7 +137,7 @@
                     t.setAlpha(parentChange.getLeash(), 1.f);
                     // and then animate this layer outside the parent (since, for example, this is
                     // the home task animating from fullscreen to part-screen).
-                    t.reparent(leash, info.getRootLeash());
+                    t.reparent(leash, info.getRoot(rootIdx).getLeash());
                     t.setLayer(leash, info.getChanges().size() - i);
                     // build the finish reparent/reposition
                     mFinishTransaction.reparent(leash, parentChange.getLeash());
@@ -145,8 +147,9 @@
                 // TODO(shell-transitions): screenshot here
                 final Rect startBounds = new Rect(change.getStartAbsBounds());
                 final Rect endBounds = new Rect(change.getEndAbsBounds());
-                startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
-                endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+                final Point rootOffset = info.getRoot(rootIdx).getOffset();
+                startBounds.offset(-rootOffset.x, -rootOffset.y);
+                endBounds.offset(-rootOffset.x, -rootOffset.y);
                 startExampleResizeAnimation(leash, startBounds, endBounds);
             }
             boolean isRootOrSplitSideRoot = change.getParent() == null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 75112b6..2e86448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -179,7 +179,9 @@
                 out.getChanges().add(info.getChanges().get(i));
             }
         }
-        out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            out.addRoot(info.getRoot(i));
+        }
         out.setAnimationOptions(info.getAnimationOptions());
         return out;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index f66c26b..63c7969 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -383,9 +383,10 @@
                     continue;
                 }
                 // No default animation for this, so just update bounds/position.
+                final int rootIdx = TransitionUtil.rootIndexFor(change, info);
                 startTransaction.setPosition(change.getLeash(),
-                        change.getEndAbsBounds().left - info.getRootOffset().x,
-                        change.getEndAbsBounds().top - info.getRootOffset().y);
+                        change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+                        change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
                 // Seamless display transition doesn't need to animate.
                 if (isSeamlessDisplayChange) continue;
                 if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
@@ -474,8 +475,10 @@
         }
 
         if (backgroundColorForTransition != 0) {
-            addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
-                    startTransaction, finishTransaction);
+            for (int i = 0; i < info.getRootCount(); ++i) {
+                addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
+                        startTransaction, finishTransaction);
+            }
         }
 
         if (postStartTransactionCallbacks.size() > 0) {
@@ -520,8 +523,10 @@
     private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
             TransitionInfo.Change change, TransitionInfo info, int animHint,
             ArrayList<Animator> animations, Runnable onAnimFinish) {
+        final int rootIdx = TransitionUtil.rootIndexFor(change, info);
         final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
-                mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+                mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
+                animHint);
         // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
         // content, and background color. The item of "animGroup" will be removed if the sub
         // animation is finished. Then if the list becomes empty, the rotation animation is done.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 27b82c0..039bde9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -75,6 +75,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -415,8 +416,8 @@
     private static void setupAnimHierarchy(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         boolean isOpening = isOpeningType(info.getType());
-        if (info.getRootLeash().isValid()) {
-            t.show(info.getRootLeash());
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            t.show(info.getRoot(i).getLeash());
         }
         final int numChanges = info.getChanges().size();
         // Put animating stuff above this line and put static stuff below it.
@@ -434,10 +435,12 @@
 
             boolean hasParent = change.getParent() != null;
 
+            final int rootIdx = TransitionUtil.rootIndexFor(change, info);
             if (!hasParent) {
-                t.reparent(leash, info.getRootLeash());
-                t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
-                        change.getStartAbsBounds().top - info.getRootOffset().y);
+                t.reparent(leash, info.getRoot(rootIdx).getLeash());
+                t.setPosition(leash,
+                        change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+                        change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
             }
             final int layer;
             // Put all the OPEN/SHOW on top
@@ -532,12 +535,6 @@
 
         if (info.getType() == TRANSIT_SLEEP) {
             if (activeIdx > 0) {
-                if (!info.getRootLeash().isValid()) {
-                    // Shell has some debug settings which makes calling binders with invalid
-                    // surfaces crash, so replace it with a "real" one.
-                    info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
-                            .setContainerLayer().build(), 0, 0);
-                }
                 // Sleep starts a process of forcing all prior transitions to finish immediately
                 finishForSleep(null /* forceFinish */);
                 return;
@@ -546,10 +543,10 @@
 
         // Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
         // screen-off, so there might no visibility change involved.
-        if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
-            // Invalid root-leash implies that the transition is empty/no-op, so just do
+        if (info.getRootCount() == 0 && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
+            // No root-leashes implies that the transition is empty/no-op, so just do
             // housekeeping and return.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
                     transitionToken, info);
             onAbort(active);
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 8c6e1e7..7595c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -139,11 +139,12 @@
         // changes should be ordered top-to-bottom in z
         final int mode = change.getMode();
 
-        t.reparent(leash, info.getRootLeash());
+        final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+        t.reparent(leash, info.getRoot(rootIdx).getLeash());
         final Rect absBounds =
                 (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
-        t.setPosition(leash, absBounds.left - info.getRootOffset().x,
-                absBounds.top - info.getRootOffset().y);
+        t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
+                absBounds.top - info.getRoot(rootIdx).getOffset().y);
 
         // Put all the OPEN/SHOW on top
         if (TransitionUtil.isOpeningType(mode)) {
@@ -179,12 +180,13 @@
             // making leashes means we have to handle them specially.
             return change.getLeash();
         }
+        final int rootIdx = TransitionUtil.rootIndexFor(change, info);
         SurfaceControl leashSurface = new SurfaceControl.Builder()
                 .setName(change.getLeash().toString() + "_transition-leash")
                 .setContainerLayer()
                 // Initial the surface visible to respect the visibility of the original surface.
                 .setHidden(false)
-                .setParent(info.getRootLeash())
+                .setParent(info.getRoot(rootIdx).getLeash())
                 .build();
         // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
         setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
@@ -261,4 +263,18 @@
         target.setRotationChange(change.getEndRotation() - change.getStartRotation());
         return target;
     }
+
+    /**
+     * Finds the "correct" root idx for a change. The change's end display is prioritized, then
+     * the start display. If there is no display, it will fallback on the 0th root in the
+     * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
+     */
+    public static int rootIndexFor(@NonNull TransitionInfo.Change change,
+            @NonNull TransitionInfo info) {
+        int rootIdx = info.findRootIndex(change.getEndDisplayId());
+        if (rootIdx >= 0) return rootIdx;
+        rootIdx = info.findRootIndex(change.getStartDisplayId());
+        if (rootIdx >= 0) return rootIdx;
+        return 0;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 72da108..3c0ef96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -23,6 +23,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
@@ -46,6 +47,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -95,6 +97,17 @@
         mDesktopActive = DesktopModeStatus.isActive(mContext);
     }
 
+    @Override
+    protected Configuration getConfigurationWithOverrides(
+            ActivityManager.RunningTaskInfo taskInfo) {
+        Configuration configuration = taskInfo.getConfiguration();
+        if (DesktopTasksController.isDesktopDensityOverrideSet()) {
+            // Density is overridden for desktop tasks. Keep system density for window decoration.
+            configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi;
+        }
+        return configuration;
+    }
+
     void setCaptionListeners(
             View.OnClickListener onCaptionButtonClickListener,
             View.OnTouchListener onCaptionTouchListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7a7ac47..ddd3b44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -131,7 +131,17 @@
         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
 
         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
-        mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+        mDecorWindowContext = mContext.createConfigurationContext(
+                getConfigurationWithOverrides(mTaskInfo));
+    }
+
+    /**
+     * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
+     *
+     * Allows values to be overridden before returning the configuration.
+     */
+    protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
+        return taskInfo.getConfiguration();
     }
 
     /**
@@ -165,7 +175,7 @@
 
         outResult.mRootView = rootView;
         rootView = null; // Clear it just in case we use it accidentally
-        final Configuration taskConfig = mTaskInfo.getConfiguration();
+        final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
         if (oldTaskConfig.densityDpi != taskConfig.densityDpi
                 || mDisplay == null
                 || mDisplay.getDisplayId() != mTaskInfo.displayId) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
index 35c374d..26b787f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
@@ -30,6 +30,7 @@
  */
 public class TransitionInfoBuilder {
     final TransitionInfo mInfo;
+    static final int DISPLAY_ID = 0;
 
     public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
         this(type, 0 /* flags */);
@@ -38,7 +39,7 @@
     public TransitionInfoBuilder(@WindowManager.TransitionType int type,
             @WindowManager.TransitionFlags int flags) {
         mInfo = new TransitionInfo(type, flags);
-        mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+        mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
     }
 
     public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -61,6 +62,7 @@
     }
 
     public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+        change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
         mInfo.addChange(change);
         return this;
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 0e14c69..108e273 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -115,6 +116,7 @@
     @Mock private Optional<OneHandedController> mMockOneHandedController;
     @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
     @Mock private DisplayInsetsController mMockDisplayInsetsController;
+    @Mock private TabletopModeController mMockTabletopModeController;
 
     @Mock private DisplayLayout mMockDisplayLayout1;
     @Mock private DisplayLayout mMockDisplayLayout2;
@@ -137,7 +139,8 @@
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor);
+                mMockDisplayInsetsController, mMockTabletopModeController,
+                mMockOneHandedController, mMockExecutor);
         mShellInit.init();
         when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
         when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -228,7 +231,8 @@
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor));
+                mMockDisplayInsetsController, mMockTabletopModeController,
+                mMockOneHandedController, mMockExecutor));
     }
 
     @Test
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 8977d3c..bfe4eaf 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -23,21 +23,55 @@
 #include "utils/Trace.h"
 
 #ifdef __ANDROID__
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkGainmapInfo.h"
 #include "renderthread/CanvasContext.h"
+#include "src/core/SkColorFilterPriv.h"
+#include "src/core/SkImageInfoPriv.h"
+#include "src/core/SkRuntimeEffectPriv.h"
 #endif
 
 namespace android::uirenderer {
 
 using namespace renderthread;
 
+static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+    // We should always have a known destination colorspace. If we don't we must be in some
+    // legacy mode where we're lost and also definitely not going to HDR
+    if (destColorspace == nullptr) {
+        return 1.f;
+    }
+
+    constexpr float GenericSdrWhiteNits = 203.f;
+    constexpr float maxPQLux = 10000.f;
+    constexpr float maxHLGLux = 1000.f;
+    skcms_TransferFunction destTF;
+    destColorspace->transferFn(&destTF);
+    if (skcms_TransferFunction_isPQish(&destTF)) {
+        return maxPQLux / GenericSdrWhiteNits;
+    } else if (skcms_TransferFunction_isHLGish(&destTF)) {
+        return maxHLGLux / GenericSdrWhiteNits;
+    } else {
+#ifdef __ANDROID__
+        CanvasContext* context = CanvasContext::getActiveContext();
+        return context ? context->targetSdrHdrRatio() : 1.f;
+#else
+        return 1.f;
+#endif
+    }
+}
+
 void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
                        const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
                        SkCanvas::SrcRectConstraint constraint,
                        const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
     ATRACE_CALL();
 #ifdef __ANDROID__
-    CanvasContext* context = CanvasContext::getActiveContext();
-    float targetSdrHdrRatio = context ? context->targetSdrHdrRatio() : 1.f;
+    auto destColorspace = c->imageInfo().refColorSpace();
+    float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
     if (targetSdrHdrRatio > 1.f && gainmapImage) {
         SkPaint gainmapPaint = *paint;
         float sX = gainmapImage->width() / (float)image->width();
@@ -48,9 +82,9 @@
         gainmapSrc.fRight *= sX;
         gainmapSrc.fTop *= sY;
         gainmapSrc.fBottom *= sY;
-        auto shader = SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc,
-                                            sampling, gainmapInfo, dst, targetSdrHdrRatio,
-                                            c->imageInfo().refColorSpace());
+        auto shader =
+                SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
+                                      gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
         gainmapPaint.setShader(shader);
         c->drawRect(dst, gainmapPaint);
     } else
@@ -58,4 +92,213 @@
         c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
 }
 
+#ifdef __ANDROID__
+
+static constexpr char gGainmapSKSL[] = R"SKSL(
+    uniform shader base;
+    uniform shader gainmap;
+    uniform colorFilter workingSpaceToLinearSrgb;
+    uniform half4 logRatioMin;
+    uniform half4 logRatioMax;
+    uniform half4 gainmapGamma;
+    uniform half4 epsilonSdr;
+    uniform half4 epsilonHdr;
+    uniform half W;
+    uniform int gainmapIsAlpha;
+    uniform int gainmapIsRed;
+    uniform int singleChannel;
+    uniform int noGamma;
+
+    half4 toDest(half4 working) {
+        half4 ls = workingSpaceToLinearSrgb.eval(working);
+        vec3 dest = fromLinearSrgb(ls.rgb);
+        return half4(dest.r, dest.g, dest.b, ls.a);
+    }
+
+    half4 main(float2 coord) {
+        half4 S = base.eval(coord);
+        half4 G = gainmap.eval(coord);
+        if (gainmapIsAlpha == 1) {
+            G = half4(G.a, G.a, G.a, 1.0);
+        }
+        if (gainmapIsRed == 1) {
+            G = half4(G.r, G.r, G.r, 1.0);
+        }
+        if (singleChannel == 1) {
+            half L;
+            if (noGamma == 1) {
+                L = mix(logRatioMin.r, logRatioMax.r, G.r);
+            } else {
+                L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
+            }
+            half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+            return toDest(half4(H.r, H.g, H.b, S.a));
+        } else {
+            half3 L;
+            if (noGamma == 1) {
+                L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
+            } else {
+                L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
+            }
+            half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+            return toDest(half4(H.r, H.g, H.b, S.a));
+        }
+    }
+)SKSL";
+
+static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
+    static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
+        auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
+        if (buildResult.effect) {
+            return buildResult.effect.release();
+        } else {
+            LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
+        }
+    }();
+    SkASSERT(effect);
+    return sk_ref_sp(effect);
+}
+
+static bool all_channels_equal(const SkColor4f& c) {
+    return c.fR == c.fG && c.fR == c.fB;
+}
+
+class DeferredGainmapShader {
+private:
+    sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
+    SkRuntimeShaderBuilder mBuilder{mShader};
+    SkGainmapInfo mGainmapInfo;
+    std::mutex mUniformGuard;
+
+    void setupChildren(const sk_sp<const SkImage>& baseImage,
+                       const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
+                       SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
+        sk_sp<SkColorSpace> baseColorSpace =
+                baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
+
+        // Determine the color space in which the gainmap math is to be applied.
+        sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
+
+        // Create a color filter to transform from the base image's color space to the color space
+        // in which the gainmap is to be applied.
+        auto colorXformSdrToGainmap =
+                SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
+
+        // The base image shader will convert into the color space in which the gainmap is applied.
+        auto baseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
+                                       ->makeWithColorFilter(colorXformSdrToGainmap);
+
+        // The gainmap image shader will ignore any color space that the gainmap has.
+        const SkMatrix gainmapRectToDstRect =
+                SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
+                                     SkRect::MakeWH(baseImage->width(), baseImage->height()));
+        auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
+                                                              &gainmapRectToDstRect);
+
+        // Create a color filter to transform from the color space in which the gainmap is applied
+        // to the intermediate destination color space.
+        auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
+                gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());
+
+        mBuilder.child("base") = std::move(baseImageShader);
+        mBuilder.child("gainmap") = std::move(gainmapImageShader);
+        mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
+    }
+
+    void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
+                              const SkGainmapInfo& gainmapInfo) {
+        const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
+        const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
+        const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
+                            gainmapInfo.fGainmapGamma.fG == 1.f &&
+                            gainmapInfo.fGainmapGamma.fB == 1.f;
+        const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
+        const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
+        const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
+        const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
+                                  all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
+                                  all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
+                                  (colorTypeFlags == kGray_SkColorChannelFlag ||
+                                   colorTypeFlags == kAlpha_SkColorChannelFlag ||
+                                   colorTypeFlags == kRed_SkColorChannelFlag);
+        mBuilder.uniform("logRatioMin") = logRatioMin;
+        mBuilder.uniform("logRatioMax") = logRatioMax;
+        mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
+        mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
+        mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
+        mBuilder.uniform("noGamma") = noGamma;
+        mBuilder.uniform("singleChannel") = singleChannel;
+        mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
+        mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
+    }
+
+    sk_sp<const SkData> build(float targetHdrSdrRatio) {
+        sk_sp<const SkData> uniforms;
+        {
+            // If we are called concurrently from multiple threads, we need to guard the call
+            // to writableUniforms() which mutates mUniform. This is otherwise safe because
+            // writeableUniforms() will make a copy if it's not unique before mutating
+            // This can happen if a BitmapShader is used on multiple canvas', such as a
+            // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
+            std::lock_guard _lock(mUniformGuard);
+            const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
+                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+                                     (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+            const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+            mBuilder.uniform("W") = W;
+            uniforms = mBuilder.uniforms();
+        }
+        return uniforms;
+    }
+
+public:
+    explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
+                                   const sk_sp<const SkImage>& gainmapImage,
+                                   const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                   SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+        mGainmapInfo = gainmapInfo;
+        setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
+        setupGenericUniforms(gainmapImage, gainmapInfo);
+    }
+
+    static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
+                                const sk_sp<const SkImage>& gainmapImage,
+                                const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+        auto deferredHandler = std::make_shared<DeferredGainmapShader>(
+                image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
+        auto callback =
+                [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
+                -> sk_sp<const SkData> {
+            return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
+        };
+        return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
+                                                       deferredHandler->mBuilder.children());
+    }
+};
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+                                  const sk_sp<const SkImage>& gainmapImage,
+                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                  SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+    return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
+                                       sampling);
+}
+
+#else  // __ANDROID__
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+                                  const sk_sp<const SkImage>& gainmapImage,
+                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                  SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+        return nullptr;
+}
+
+#endif  // __ANDROID__
+
 }  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 7c56d94..4ed2445 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -30,4 +30,9 @@
                        SkCanvas::SrcRectConstraint constraint,
                        const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo);
 
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+                                  const sk_sp<const SkImage>& gainmapImage,
+                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                  SkTileMode tileModeY, const SkSamplingOptions& sampling);
+
 }  // namespace android::uirenderer
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 75d45e5..7eb79be 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,9 @@
 #undef LOG_TAG
 #define LOG_TAG "ShaderJNI"
 
+#include <vector>
+
+#include "Gainmap.h"
 #include "GraphicsJNI.h"
 #include "SkBitmap.h"
 #include "SkBlendMode.h"
@@ -17,10 +20,9 @@
 #include "SkShader.h"
 #include "SkString.h"
 #include "SkTileMode.h"
+#include "effects/GainmapRenderer.h"
 #include "include/effects/SkRuntimeEffect.h"
 
-#include <vector>
-
 using namespace android::uirenderer;
 
 /**
@@ -74,7 +76,20 @@
     if (bitmapHandle) {
         // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
         // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
-        image = android::bitmap::toBitmap(bitmapHandle).makeImage();
+        auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
+        image = bitmap.makeImage();
+
+        if (!isDirectSampled && bitmap.hasGainmap()) {
+            sk_sp<SkShader> gainmapShader = MakeGainmapShader(
+                    image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+                    (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+            if (gainmapShader) {
+                if (matrix) {
+                    gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
+                }
+                return reinterpret_cast<jlong>(gainmapShader.release());
+            }
+        }
     }
 
     if (!image.get()) {
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 24cfc9d..c398405 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -63,24 +63,23 @@
     mLocked.pointerSprite.clear();
 }
 
-bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
-                                      float* outMaxY) const {
+std::optional<FloatRect> MouseCursorController::getBounds() const {
     std::scoped_lock lock(mLock);
 
-    return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+    return getBoundsLocked();
 }
 
-bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
-                                            float* outMaxY) const REQUIRES(mLock) {
+std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
     if (!mLocked.viewport.isValid()) {
-        return false;
+        return {};
     }
 
-    *outMinX = mLocked.viewport.logicalLeft;
-    *outMinY = mLocked.viewport.logicalTop;
-    *outMaxX = mLocked.viewport.logicalRight - 1;
-    *outMaxY = mLocked.viewport.logicalBottom - 1;
-    return true;
+    return FloatRect{
+            static_cast<float>(mLocked.viewport.logicalLeft),
+            static_cast<float>(mLocked.viewport.logicalTop),
+            static_cast<float>(mLocked.viewport.logicalRight - 1),
+            static_cast<float>(mLocked.viewport.logicalBottom - 1),
+    };
 }
 
 void MouseCursorController::move(float deltaX, float deltaY) {
@@ -121,31 +120,19 @@
 }
 
 void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
-    float minX, minY, maxX, maxY;
-    if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
-        if (x <= minX) {
-            mLocked.pointerX = minX;
-        } else if (x >= maxX) {
-            mLocked.pointerX = maxX;
-        } else {
-            mLocked.pointerX = x;
-        }
-        if (y <= minY) {
-            mLocked.pointerY = minY;
-        } else if (y >= maxY) {
-            mLocked.pointerY = maxY;
-        } else {
-            mLocked.pointerY = y;
-        }
-        updatePointerLocked();
-    }
+    const auto bounds = getBoundsLocked();
+    if (!bounds) return;
+
+    mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
+    mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+
+    updatePointerLocked();
 }
 
-void MouseCursorController::getPosition(float* outX, float* outY) const {
+FloatPoint MouseCursorController::getPosition() const {
     std::scoped_lock lock(mLock);
 
-    *outX = mLocked.pointerX;
-    *outY = mLocked.pointerY;
+    return {mLocked.pointerX, mLocked.pointerY};
 }
 
 int32_t MouseCursorController::getDisplayId() const {
@@ -235,10 +222,9 @@
     // Reset cursor position to center if size or display changed.
     if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
         oldDisplayHeight != newDisplayHeight) {
-        float minX, minY, maxX, maxY;
-        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
-            mLocked.pointerX = (minX + maxX) * 0.5f;
-            mLocked.pointerY = (minY + maxY) * 0.5f;
+        if (const auto bounds = getBoundsLocked(); bounds) {
+            mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
+            mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
             // Reload icon resources for density may be changed.
             loadResourcesLocked(getAdditionalMouseResources);
         } else {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index db0ab56..26be2a8 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,12 +43,12 @@
     MouseCursorController(PointerControllerContext& context);
     ~MouseCursorController();
 
-    bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    std::optional<FloatRect> getBounds() const;
     void move(float deltaX, float deltaY);
     void setButtonState(int32_t buttonState);
     int32_t getButtonState() const;
     void setPosition(float x, float y);
-    void getPosition(float* outX, float* outY) const;
+    FloatPoint getPosition() const;
     int32_t getDisplayId() const;
     void fade(PointerControllerInterface::Transition transition);
     void unfade(PointerControllerInterface::Transition transition);
@@ -102,7 +102,7 @@
 
     } mLocked GUARDED_BY(mLock);
 
-    bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    std::optional<FloatRect> getBoundsLocked() const;
     void setPositionLocked(float x, float y);
 
     void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index fedf58d..544edc2 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -114,16 +114,15 @@
 PointerController::~PointerController() {
     mDisplayInfoListener->onPointerControllerDestroyed();
     mUnregisterWindowInfosListener(mDisplayInfoListener);
-    mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
+    mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0});
 }
 
 std::mutex& PointerController::getLock() const {
     return mDisplayInfoListener->mLock;
 }
 
-bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
-                                  float* outMaxY) const {
-    return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
+std::optional<FloatRect> PointerController::getBounds() const {
+    return mCursorController.getBounds();
 }
 
 void PointerController::move(float deltaX, float deltaY) {
@@ -156,15 +155,13 @@
     mCursorController.setPosition(transformed.x, transformed.y);
 }
 
-void PointerController::getPosition(float* outX, float* outY) const {
+FloatPoint PointerController::getPosition() const {
     const int32_t displayId = mCursorController.getDisplayId();
-    mCursorController.getPosition(outX, outY);
+    const auto p = mCursorController.getPosition();
     {
         std::scoped_lock lock(getLock());
         const auto& transform = getTransformForDisplayLocked(displayId);
-        const auto xy = transform.inverse().transform(*outX, *outY);
-        *outX = xy.x;
-        *outY = xy.y;
+        return FloatPoint{transform.inverse().transform(p.x, p.y)};
     }
 }
 
@@ -262,19 +259,31 @@
 }
 
 void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
-    std::scoped_lock lock(getLock());
+    struct PointerDisplayChangeArgs {
+        int32_t displayId;
+        FloatPoint cursorPosition;
+    };
+    std::optional<PointerDisplayChangeArgs> pointerDisplayChanged;
 
-    bool getAdditionalMouseResources = false;
-    if (mLocked.presentation == PointerController::Presentation::POINTER ||
-        mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
-        getAdditionalMouseResources = true;
-    }
-    mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
-    if (viewport.displayId != mLocked.pointerDisplayId) {
-        float xPos, yPos;
-        mCursorController.getPosition(&xPos, &yPos);
-        mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
-        mLocked.pointerDisplayId = viewport.displayId;
+    { // acquire lock
+        std::scoped_lock lock(getLock());
+
+        bool getAdditionalMouseResources = false;
+        if (mLocked.presentation == PointerController::Presentation::POINTER ||
+            mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
+            getAdditionalMouseResources = true;
+        }
+        mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+        if (viewport.displayId != mLocked.pointerDisplayId) {
+            mLocked.pointerDisplayId = viewport.displayId;
+            pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()};
+        }
+    } // release lock
+
+    if (pointerDisplayChanged) {
+        // Notify the policy without holding the pointer controller lock.
+        mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId,
+                                                        pointerDisplayChanged->cursorPosition);
     }
 }
 
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 48d5a57..6d3557c 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -50,12 +50,12 @@
 
     ~PointerController() override;
 
-    virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    virtual std::optional<FloatRect> getBounds() const;
     virtual void move(float deltaX, float deltaY);
     virtual void setButtonState(int32_t buttonState);
     virtual int32_t getButtonState() const;
     virtual void setPosition(float x, float y);
-    virtual void getPosition(float* outX, float* outY) const;
+    virtual FloatPoint getPosition() const;
     virtual int32_t getDisplayId() const;
     virtual void fade(Transition transition);
     virtual void unfade(Transition transition);
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 96d83a5..f6f5d3b 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -81,7 +81,7 @@
     virtual PointerIconStyle getDefaultPointerIconId() = 0;
     virtual PointerIconStyle getDefaultStylusIconId() = 0;
     virtual PointerIconStyle getCustomPointerIconId() = 0;
-    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
+    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
 };
 
 /*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index c820d00..2378d42 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -60,7 +60,7 @@
     virtual PointerIconStyle getDefaultPointerIconId() override;
     virtual PointerIconStyle getDefaultStylusIconId() override;
     virtual PointerIconStyle getCustomPointerIconId() override;
-    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
+    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
 
     bool allResourcesAreLoaded();
     bool noResourcesAreLoaded();
@@ -143,8 +143,8 @@
 }
 
 void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
-                                                                     float /*xPos*/,
-                                                                     float /*yPos*/) {
+                                                                     const FloatPoint& /*position*/
+) {
     latestPointerDisplayId = displayId;
 }
 
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 23f87ab..f86b9af 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1566,7 +1566,7 @@
         FileInputStream in = null;
         try {
             in = new FileInputStream(fileDescriptor);
-            loadAttributes(in);
+            loadAttributes(in, fileDescriptor);
         } finally {
             closeQuietly(in);
             if (isFdDuped) {
@@ -1637,7 +1637,7 @@
                 mSeekableFileDescriptor = null;
             }
         }
-        loadAttributes(inputStream);
+        loadAttributes(inputStream, null);
     }
 
     /**
@@ -1963,7 +1963,7 @@
      * This function decides which parser to read the image data according to the given input stream
      * type and the content of the input stream.
      */
-    private void loadAttributes(@NonNull InputStream in) {
+    private void loadAttributes(@NonNull InputStream in, @Nullable FileDescriptor fd) {
         if (in == null) {
             throw new NullPointerException("inputstream shouldn't be null");
         }
@@ -1993,7 +1993,7 @@
                         break;
                     }
                     case IMAGE_TYPE_HEIF: {
-                        getHeifAttributes(inputStream);
+                        getHeifAttributes(inputStream, fd);
                         break;
                     }
                     case IMAGE_TYPE_ORF: {
@@ -2580,7 +2580,7 @@
             } else if (isSeekableFD(in.getFD())) {
                 mSeekableFileDescriptor = in.getFD();
             }
-            loadAttributes(in);
+            loadAttributes(in, null);
         } finally {
             closeQuietly(in);
             if (modernFd != null) {
@@ -3068,59 +3068,66 @@
         }
     }
 
-    private void getHeifAttributes(ByteOrderedDataInputStream in) throws IOException {
+    private void getHeifAttributes(ByteOrderedDataInputStream in, @Nullable FileDescriptor fd)
+            throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         try {
-            retriever.setDataSource(new MediaDataSource() {
-                long mPosition;
+            if (fd != null) {
+                retriever.setDataSource(fd);
+            } else {
+                retriever.setDataSource(new MediaDataSource() {
+                    long mPosition;
 
-                @Override
-                public void close() throws IOException {}
+                    @Override
+                    public void close() throws IOException {}
 
-                @Override
-                public int readAt(long position, byte[] buffer, int offset, int size)
-                        throws IOException {
-                    if (size == 0) {
-                        return 0;
-                    }
-                    if (position < 0) {
+                    @Override
+                    public int readAt(long position, byte[] buffer, int offset, int size)
+                            throws IOException {
+                        if (size == 0) {
+                            return 0;
+                        }
+                        if (position < 0) {
+                            return -1;
+                        }
+                        try {
+                            if (mPosition != position) {
+                                // We don't allow seek to positions after the available bytes,
+                                // the input stream won't be able to seek back then.
+                                // However, if we hit an exception before (mPosition set to -1),
+                                // let it try the seek in hope it might recover.
+                                if (mPosition >= 0 && position >= mPosition + in.available()) {
+                                    return -1;
+                                }
+                                in.seek(position);
+                                mPosition = position;
+                            }
+
+                            // If the read will cause us to go over the available bytes,
+                            // reduce the size so that we stay in the available range.
+                            // Otherwise the input stream may not be able to seek back.
+                            if (size > in.available()) {
+                                size = in.available();
+                            }
+
+                            int bytesRead = in.read(buffer, offset, size);
+                            if (bytesRead >= 0) {
+                                mPosition += bytesRead;
+                                return bytesRead;
+                            }
+                        } catch (IOException e) {
+                            // absorb the exception and fall through to the 'failed read' path below
+                        }
+                        mPosition = -1; // need to seek on next read
                         return -1;
                     }
-                    try {
-                        if (mPosition != position) {
-                            // We don't allow seek to positions after the available bytes,
-                            // the input stream won't be able to seek back then.
-                            // However, if we hit an exception before (mPosition set to -1),
-                            // let it try the seek in hope it might recover.
-                            if (mPosition >= 0 && position >= mPosition + in.available()) {
-                                return -1;
-                            }
-                            in.seek(position);
-                            mPosition = position;
-                        }
 
-                        // If the read will cause us to go over the available bytes,
-                        // reduce the size so that we stay in the available range.
-                        // Otherwise the input stream may not be able to seek back.
-                        if (size > in.available()) {
-                            size = in.available();
-                        }
-
-                        int bytesRead = in.read(buffer, offset, size);
-                        if (bytesRead >= 0) {
-                            mPosition += bytesRead;
-                            return bytesRead;
-                        }
-                    } catch (IOException e) {}
-                    mPosition = -1; // need to seek on next read
-                    return -1;
-                }
-
-                @Override
-                public long getSize() throws IOException {
-                    return -1;
-                }
-            });
+                    @Override
+                    public long getSize() throws IOException {
+                        return -1;
+                    }
+                });
+            }
 
             String exifOffsetStr = retriever.extractMetadata(
                     MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 5bc8c04..0a0a626 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -436,7 +436,7 @@
                     if (mEventHandler != null) {
                         mEventHandler.sendMessage(
                                 mEventHandler.obtainMessage(
-                                        EventHandler.MSG_CAS_EVENT, event, arg, data));
+                                        EventHandler.MSG_CAS_EVENT, event, arg, toBytes(data)));
                     }
                 }
 
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 9c629bb..b1b7d40 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -1210,7 +1210,7 @@
 
     /**
      * A key describing the desired bitrate mode to be used by an encoder.
-     * Constants are declared in {@link MediaCodecInfo.CodecCapabilities}.
+     * Constants are declared in {@link MediaCodecInfo.EncoderCapabilities}.
      *
      * @see MediaCodecInfo.EncoderCapabilities#isBitrateModeSupported(int)
      */
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 7d6a8b4..ef2b5a5 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -149,10 +149,45 @@
     /**
      * This constant is used as a {@link Bundle} key for TV messages. The value of the key
      * identifies the stream on the TV input source for which the watermark event is relevant to.
+     *
+     * <p> Type: String
      */
     public static final String TV_MESSAGE_KEY_STREAM_ID =
             "android.media.tv.TvInputManager.stream_id";
 
+    /**
+     * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+     * identifies the subtype of the data, such as the format of the CC data. The format
+     * found at this key can then be used to identify how to parse the data at
+     * {@link #TV_MESSAGE_KEY_RAW_DATA}.
+     *
+     * To parse the raw data bsed on the subtype, please refer to the official documentation of the
+     * concerning subtype. For example, for the subtype "ATSC A/335" for watermarking, the
+     * document for A/335 from the ATSC standard details how this data is formatted.
+     *
+     * Some other examples of common formats include:
+     * <ul>
+     *     <li>Watermarking - ATSC A/336</li>
+     *     <li>Closed Captioning - CTA 608-E</li>
+     * </ul>
+     *
+     * <p> Type: String
+     */
+    public static final String TV_MESSAGE_KEY_SUBTYPE =
+            "android.media.tv.TvInputManager.subtype";
+
+    /**
+     * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+     * stores the raw data contained in this TV Message. The format of this data is determined
+     * by the format defined by the subtype, found using the key at
+     * {@link #TV_MESSAGE_KEY_SUBTYPE}. See {@link #TV_MESSAGE_KEY_SUBTYPE} for more
+     * information on how to parse this data.
+     *
+     * <p> Type: byte[]
+     */
+    public static final String TV_MESSAGE_KEY_RAW_DATA =
+            "android.media.tv.TvInputManager.raw_data";
+
     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
     static final int VIDEO_UNAVAILABLE_REASON_END = 18;
 
@@ -802,7 +837,13 @@
          *
          * @param session A {@link TvInputManager.Session} associated with this callback.
          * @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
+         *
          */
         public void onTvMessage(Session session, @TvInputManager.TvMessageType int type,
                 Bundle data) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3c6ed91..4e380c4 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1030,7 +1030,12 @@
          *
          * @param type The of message that was sent, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The data sent with the message.
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void notifyTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
@@ -1500,7 +1505,12 @@
          *
          * @param type The type of message received, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
                 @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 19a2e5d..8a886832 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -1254,7 +1254,12 @@
          * @param inputId The ID of the TV input bound to this view.
          * @param type The type of message received, such as
          *             {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@NonNull String inputId,
                 @TvInputManager.TvMessageType int type, @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7dfe16a..69ff9c6 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -919,7 +919,12 @@
          *
          * @param type The type of message received, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 1a0319b..80a1435 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -944,7 +944,12 @@
      *
      * @param type The type of message received, such as
      * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-     * @param data The raw data of the message
+     * @param data The raw data of the message. The bundle keys are:
+     *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+     *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+     *             how to parse this data.
      */
     public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
             @NonNull Bundle data) {
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index e91d35b..f4e89a14 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="app_name">CarrierDefaultApp</string>
+    <string name="app_name">Carrier Communications</string>
     <string name="android_system_label">Mobile Carrier</string>
     <string name="portal_notification_id">Mobile data has run out</string>
     <string name="no_data_notification_id">Your mobile data has been deactivated</string>
@@ -17,9 +17,9 @@
     <!-- Telephony notification channel name for performance boost notifications. -->
     <string name="performance_boost_notification_channel">Performance boost</string>
     <!-- Notification title text for the performance boost notification. -->
-    <string name="performance_boost_notification_title">Improve your app experience</string>
+    <string name="performance_boost_notification_title">5G options from your carrier</string>
     <!-- Notification detail text for the performance boost notification. -->
-    <string name="performance_boost_notification_detail">Tap to visit %s\'s website and learn more</string>
+    <string name="performance_boost_notification_detail">Visit %s\'s website to see options for your app experience</string>
     <!-- Notification button text to cancel the performance boost notification. -->
     <string name="performance_boost_notification_button_not_now">Not now</string>
     <!-- Notification button text to manage the performance boost notification. -->
diff --git a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
index 1614188..9c2064c 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
@@ -22,89 +22,89 @@
 
 key GRAVE {
     label:                              '`'
-    base, capslock:                     '\u0630'
+    base:                               '\u0630'
     shift:                              '\u0651'
 }
 
 key 1 {
     label:                              '1'
     base:                               '\u0661'
-    shift:                              '!'
     capslock:                           '1'
+    shift:                              '!'
 }
 
 key 2 {
     label:                              '2'
     base:                               '\u0662'
-    shift:                              '@'
     capslock:                           '2'
+    shift:                              '@'
 }
 
 key 3 {
     label:                              '3'
     base:                               '\u0663'
-    shift:                              '#'
     capslock:                           '3'
+    shift:                              '#'
 }
 
 key 4 {
     label:                              '4'
     base:                               '\u0664'
-    shift:                              '$'
     capslock:                           '4'
+    shift:                              '$'
 }
 
 key 5 {
     label:                              '5'
     base:                               '\u0665'
-    shift:                              '%'
     capslock:                           '5'
+    shift:                              '%'
 }
 
 key 6 {
     label:                              '6'
     base:                               '\u0666'
-    shift:                              '^'
     capslock:                           '6'
+    shift:                              '^'
 }
 
 key 7 {
     label:                              '7'
     base:                               '\u0667'
-    shift:                              '&'
     capslock:                           '7'
+    shift:                              '&'
 }
 
 key 8 {
     label:                              '8'
     base:                               '\u0668'
-    shift:                              '*'
     capslock:                           '8'
+    shift:                              '*'
 }
 
 key 9 {
     label:                              '9'
     base:                               '\u0669'
-    shift:                              ')'
     capslock:                           '9'
+    shift:                              ')'
 }
 
 key 0 {
     label:                              '0'
     base:                               '\u0660'
-    shift:                              '('
     capslock:                           '0'
+    shift:                              '('
 }
 
 key MINUS {
     label:                              '-'
-    base, capslock:                     '-'
+    base:                               '-'
     shift:                              '_'
 }
 
 key EQUALS {
     label:                              '='
-    base, capslock:                     '='
+    base:                               '='
     shift:                              '+'
 }
 
@@ -112,79 +112,79 @@
 
 key Q {
     label:                              'Q'
-    base, capslock:                     '\u0636'
+    base:                               '\u0636'
     shift:                              '\u064e'
 }
 
 key W {
     label:                              'W'
-    base, capslock:                     '\u0635'
+    base:                               '\u0635'
     shift:                              '\u064b'
 }
 
 key E {
     label:                              'E'
-    base, capslock:                     '\u062b'
+    base:                               '\u062b'
     shift:                              '\u064f'
 }
 
 key R {
     label:                              'R'
-    base, capslock:                     '\u0642'
+    base:                               '\u0642'
     shift:                              '\u064c'
 }
 
 key T {
     label:                              'T'
-    base, capslock:                     '\u0641'
+    base:                               '\u0641'
     shift:                              '\ufef9'
 }
 
 key Y {
     label:                              'Y'
-    base, capslock:                     '\u063a'
+    base:                               '\u063a'
     shift:                              '\u0625'
 }
 
 key U {
     label:                              'U'
-    base, capslock:                     '\u0639'
+    base:                               '\u0639'
     shift:                              '\u2018'
 }
 
 key I {
     label:                              'I'
-    base, capslock:                     '\u0647'
+    base:                               '\u0647'
     shift:                              '\u00f7'
 }
 
 key O {
     label:                              'O'
-    base, capslock:                     '\u062e'
+    base:                               '\u062e'
     shift:                              '\u00d7'
 }
 
 key P {
     label:                              'P'
-    base, capslock:                     '\u062d'
+    base:                               '\u062d'
     shift:                              '\u061b'
 }
 
 key LEFT_BRACKET {
     label:                              ']'
-    base, capslock:                     '\u062c'
+    base:                               '\u062c'
     shift:                              '>'
 }
 
 key RIGHT_BRACKET {
     label:                              '['
-    base, capslock:                     '\u062f'
+    base:                               '\u062f'
     shift:                              '<'
 }
 
 key BACKSLASH {
     label:                              '\\'
-    base, capslock:                     '\\'
+    base:                               '\\'
     shift:                              '|'
 }
 
@@ -192,67 +192,67 @@
 
 key A {
     label:                              'A'
-    base, capslock:                     '\u0634'
+    base:                               '\u0634'
     shift:                              '\u0650'
 }
 
 key S {
     label:                              'S'
-    base, capslock:                     '\u0633'
+    base:                               '\u0633'
     shift:                              '\u064d'
 }
 
 key D {
     label:                              'D'
-    base, capslock:                     '\u064a'
+    base:                               '\u064a'
     shift:                              ']'
 }
 
 key F {
     label:                              'F'
-    base, capslock:                     '\u0628'
+    base:                               '\u0628'
     shift:                              '['
 }
 
 key G {
     label:                              'G'
-    base, capslock:                     '\u0644'
+    base:                               '\u0644'
     shift:                              '\ufef7'
 }
 
 key H {
     label:                              'H'
-    base, capslock:                     '\u0627'
+    base:                               '\u0627'
     shift:                              '\u0623'
 }
 
 key J {
     label:                              'J'
-    base, capslock:                     '\u062a'
+    base:                               '\u062a'
     shift:                              '\u0640'
 }
 
 key K {
     label:                              'K'
-    base, capslock:                     '\u0646'
+    base:                               '\u0646'
     shift:                              '\u060c'
 }
 
 key L {
     label:                              'L'
-    base, capslock:                     '\u0645'
+    base:                               '\u0645'
     shift:                              '/'
 }
 
 key SEMICOLON {
     label:                              ';'
-    base, capslock:                     '\u0643'
+    base:                               '\u0643'
     shift:                              ':'
 }
 
 key APOSTROPHE {
     label:                              '\''
-    base, capslock:                     '\u0637'
+    base:                               '\u0637'
     shift:                              '"'
 }
 
@@ -260,60 +260,60 @@
 
 key Z {
     label:                              'Z'
-    base, capslock:                     '\u0626'
+    base:                               '\u0626'
     shift:                              '~'
 }
 
 key X {
     label:                              'X'
-    base, capslock:                     '\u0621'
+    base:                               '\u0621'
     shift:                              '\u0652'
 }
 
 key C {
     label:                              'C'
-    base, capslock:                     '\u0624'
+    base:                               '\u0624'
     shift:                              '}'
 }
 
 key V {
     label:                              'V'
-    base, capslock:                     '\u0631'
+    base:                               '\u0631'
     shift:                              '{'
 }
 
 key B {
     label:                              'B'
-    base, capslock:                     '\ufefb'
+    base:                               '\ufefb'
     shift:                              '\ufef5'
 }
 
 key N {
     label:                              'N'
-    base, capslock:                     '\u0649'
+    base:                               '\u0649'
     shift:                              '\u0622'
 }
 
 key M {
     label:                              'M'
-    base, capslock:                     '\u0629'
+    base:                               '\u0629'
     shift:                              '\u2019'
 }
 
 key COMMA {
     label:                              ','
-    base, capslock:                     '\u0648'
+    base:                               '\u0648'
     shift:                              ','
 }
 
 key PERIOD {
     label:                              '.'
-    base, capslock:                     '\u0632'
+    base:                               '\u0632'
     shift:                              '.'
 }
 
 key SLASH {
     label:                              '/'
-    base, capslock:                     '\u0638'
+    base:                               '\u0638'
     shift:                              '\u061f'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
index 69490cc..3f5e894 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
@@ -107,72 +107,84 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              '\u0130'
     base:                               'i'
     shift, capslock:                    '\u0130'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift:                              '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key RIGHT_BRACKET {
     label:                              '\u011e'
     base:                               '\u011f'
     shift:                              '\u011e'
+    shift+capslock:                     '\u011f'
 }
 
 key BACKSLASH {
@@ -187,66 +199,77 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              'I'
     base:                               '\u0131'
     shift:                              'I'
+    shift+capslock:                     '\u0131'
 }
 
 key APOSTROPHE {
     label:                              '\u018f'
     base:                               '\u0259'
     shift:                              '\u018f'
+    shift+capslock:                     '\u0259'
 }
 
 ### ROW 4
@@ -255,54 +278,63 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift:                              '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key PERIOD {
     label:                              '\u015e'
     base:                               '\u015f'
     shift:                              '\u015e'
+    shift+capslock:                     '\u015f'
 }
 
 key SLASH {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm b/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
index 3deb9dd..6751e1d 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
@@ -24,6 +24,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -106,163 +107,203 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 key O {
     label:                              '\u040E'
     base:                               '\u045E'
     shift, capslock:                    '\u040E'
+    shift+capslock:                     '\u045E'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
-    ralt+shift:                         '{'
+    shift+ralt:                         '{'
 }
 key RIGHT_BRACKET {
     label:                              '\u0027'
     base:                               '\u0027'
-    shift, capslock:                    '\u0027'
     ralt:                               ']'
-    ralt+shift:                         '}'
+    shift+ralt:                         '}'
 }
 ### ROW 3
 key A {
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 key S {
     label:                              '\u042b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
-    ralt+shift:                         ':'
+    shift+ralt:                         ':'
 }
 key APOSTROPHE {
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               '\''
-    ralt+shift:                         '"'
+    shift+ralt:                         '"'
 }
 key BACKSLASH {
     label:                              '\\'
@@ -275,69 +316,85 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                      '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 key B {
     label:                              '\u0406'
     base:                               '\u0456'
     shift, capslock:                    '\u0406'
+    shift+capslock:                     '\u0456'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
-    ralt+shift:                         '<'
+    shift+ralt:                         '<'
 }
 key PERIOD {
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
-    ralt+shift:                         '>'
+    shift+ralt:                         '>'
 }
 key SLASH {
     label:                              '.'
     base:                               '.'
     shift:                              ','
     ralt:                               '/'
-    ralt+shift:                         '?'
+    shift+ralt:                         '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm b/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
index f2c39ce..d529311 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
@@ -122,18 +122,21 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -141,42 +144,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -199,60 +209,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key APOSTROPHE {
@@ -282,36 +302,42 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
index 140c7ac..ad3199f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
@@ -115,6 +115,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '/'
 }
 
@@ -122,6 +123,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '?'
 }
 
@@ -129,6 +131,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -136,42 +139,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -193,60 +203,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key APOSTROPHE {
@@ -258,7 +278,7 @@
 key BACKSLASH {
     label:                              ']'
     base:                               ']'
-    shift, capslock:                    '}'
+    shift:                              '}'
     ralt:                               '\u00ba'
 }
 
@@ -274,18 +294,21 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u20a2'
 }
 
@@ -293,24 +316,28 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'n'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
index c56367e..94ffbd0 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
@@ -27,7 +27,7 @@
 key GRAVE {
     label:                              '`'
     base:                               '`'
-    shift, capslock:                    '~'
+    shift:                              '~'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -123,89 +123,109 @@
     label:                              ','
     base:                               ','
     shift:                              '\u044b'
-    capslock:                           '\u042b'
+    shift+capslock:                     '\u042b'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               '['
-    ralt+shift:                         '{'
+    shift+ralt:                         '{'
 }
 
 key RIGHT_BRACKET {
@@ -213,7 +233,7 @@
     base:                               ';'
     shift:                              '\u00a7'
     ralt:                               ']'
-    ralt+shift:                         '}'
+    shift+ralt:                         '}'
 }
 
 ### ROW 3
@@ -222,78 +242,97 @@
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -302,6 +341,7 @@
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -328,62 +368,77 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u042a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -392,6 +447,7 @@
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
@@ -400,6 +456,7 @@
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               '/'
     ralt+shift:                         '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm b/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
index 8878807..6314158 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
@@ -28,6 +28,7 @@
     label:                              '`'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -122,88 +123,108 @@
 key Q {
     label:                              '\u0447'
     base:                               '\u0447'
-    shift:                              '\u0427'
-    capslock:                           '\u0427'
+    shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0448'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0435'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u0440'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0442'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u044a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0443'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0438'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u043e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u043f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u044f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -211,7 +232,8 @@
 key RIGHT_BRACKET {
     label:                              '\u0449'
     base:                               '\u0449'
-    shift:                              '\u0429'
+    shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -219,9 +241,8 @@
 key BACKSLASH {
     label:                              '\u044c'
     base:                               '\u044c'
-    shift:                              '\u042c'
-    capslock:                           '\u042c'
-    shift+capslock:                     '\u040d'
+    shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -232,78 +253,96 @@
     label:                              '\u0430'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u0441'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0434'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0444'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u0433'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0445'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u0439'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u043a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u043b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
-    shift, capslock:                    ':'
+    shift:                              ':'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -311,7 +350,7 @@
 key APOSTROPHE {
     label:                              '\''
     base:                               '\''
-    shift, capslock:                    '"'
+    shift:                              '"'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -322,6 +361,7 @@
     label:                              '\u045d'
     base:                               '\u045d'
     shift, capslock:                    '\u040d'
+    shift+capslock:                     '\u045d'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -330,62 +370,76 @@
     label:                              '\u0437'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0436'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0446'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u0432'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0431'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u043d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u043c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              ','
     base:                               ','
-    shift, capslock:                    '\u201e'
+    shift:                              '\u201e'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -393,7 +447,7 @@
 key PERIOD {
     label:                              '.'
     base:                               '.'
-    shift, capslock:                    '\u201c'
+    shift:                              '\u201c'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
@@ -401,7 +455,7 @@
 key SLASH {
     label:                              '/'
     base:                               '/'
-    shift, capslock:                    '?'
+    shift:                              '?'
     ralt:                               '/'
     ralt+shift:                         '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm b/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
index 96445a4..1c774cc 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
@@ -122,6 +122,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\\'
 }
 
@@ -129,6 +130,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '|'
 }
 
@@ -136,6 +138,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -143,48 +146,56 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0160'
     base:                               '\u0161'
     shift, capslock:                    '\u0160'
+    shift+capslock:                     '\u0161'
     ralt:                               '\u00f7'
 }
 
@@ -192,6 +203,7 @@
     label:                              '\u0110'
     base:                               '\u0111'
     shift, capslock:                    '\u0110'
+    shift+capslock:                     '\u0111'
     ralt:                               '\u00d7'
 }
 
@@ -201,24 +213,28 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '['
 }
 
@@ -226,6 +242,7 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               ']'
 }
 
@@ -233,40 +250,48 @@
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0268'
-    ralt+shift, ralt+capslock:          '\u0197'
+    shift+ralt, capslock+ralt:          '\u0197'
+    shift+capslock+ralt:                '\u0268'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0142'
-    ralt+shift, ralt+capslock:          '\u0141'
+    shift+ralt, capslock+ralt:          '\u0141'
+    shift+capslock+ralt:                '\u0142'
 }
 
 key SEMICOLON {
     label:                              '\u010c'
     base:                               '\u010d'
     shift, capslock:                    '\u010c'
+    shift+capslock:                     '\u010d'
 }
 
 key APOSTROPHE {
     label:                              '\u0106'
     base:                               '\u0107'
     shift, capslock:                    '\u0106'
+    shift+capslock:                     '\u0107'
     ralt:                               '\u00df'
 }
 
@@ -274,6 +299,7 @@
     label:                              '\u017d'
     base:                               '\u017e'
     shift, capslock:                    '\u017d'
+    shift+capslock:                     '\u017e'
     ralt:                               '\u00a4'
 }
 
@@ -289,24 +315,28 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -314,6 +344,7 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '{'
 }
 
@@ -321,6 +352,7 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '}'
 }
 
@@ -328,6 +360,7 @@
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00a7'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_czech.kcm b/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
index 32750e0..08b012e 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
@@ -131,18 +131,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -150,42 +153,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -211,54 +221,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -300,24 +319,28 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -325,18 +348,21 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
index 457d4da..cad262b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
@@ -131,18 +131,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -150,42 +153,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -211,54 +221,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -300,24 +319,28 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -325,18 +348,21 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_danish.kcm b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
index 9168d12..83ee8c3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c6'
     base:                               '\u00e6'
     shift, capslock:                    '\u00c6'
+    shift+capslock:                     '\u00e6'
     ralt:                               '\u00e4'
-    ralt+capslock, shift+ralt:          '\u00c4'
+    shift+ralt, capslock+ralt:          '\u00c4'
+    shift+capslock+ralt:                '\u00e4'
 }
 
 key APOSTROPHE {
     label:                              '\u00d8'
     base:                               '\u00f8'
     shift, capslock:                    '\u00d8'
+    shift+capslock:                     '\u00f8'
     ralt:                               '\u00f6'
-    ralt+capslock, shift+ralt:          '\u00d6'
+    shift+ralt, capslock+ralt:          '\u00d6'
+    shift+capslock+ralt:                '\u00f6'
 }
 
 key BACKSLASH {
@@ -299,53 +333,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
index 6d9c2e5..93a5082 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
@@ -108,68 +108,82 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u00e9'
-    shift+ralt:                         '\u00c9'
+    shift+ralt, capslock+ralt:          '\u00c9'
+    shift+capslock+ralt:                '\u00e9'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u00fa'
-    shift+ralt:                         '\u00da'
+    shift+ralt, capslock+ralt:          '\u00da'
+    shift+capslock+ralt:                '\u00fa'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ed'
-    shift+ralt:                         '\u00cd'
+    shift+ralt, capslock+ralt:          '\u00cd'
+    shift+capslock+ralt:                '\u00ed'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f3'
-    shift+ralt:                         '\u00d3'
+    shift+ralt, capslock+ralt:          '\u00d3'
+    shift+capslock+ralt:                '\u00f3'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -190,56 +204,66 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    shift+ralt:                         '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -274,42 +298,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
index 050b149..da76448 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
@@ -106,60 +106,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -186,54 +196,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -254,42 +273,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
index 72e6d04..e52ccf0 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
@@ -125,60 +125,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
     shift, capslock:                    ':'
+    shift+capslock:                     ':'
 }
 
 key LEFT_BRACKET {
@@ -205,54 +215,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
@@ -273,42 +292,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
index df6a3fd..6ff627b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
@@ -160,42 +160,49 @@
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SLASH {
@@ -222,60 +229,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key MINUS {
@@ -296,52 +313,61 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
index aa31493..dff17b3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
@@ -121,30 +121,37 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e4'
     shift+ralt, capslock+ralt:          '\u00c4'
+    shift+capslock+ralt:                '\u00e4'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '\u00e5'
     shift+ralt, capslock+ralt:          '\u00c5'
+    shift+capslock+ralt:                '\u00e5'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u00e9'
     shift+ralt, capslock+ralt:          '\u00c9'
+    shift+capslock+ralt:                '\u00e9'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
     ralt:                               '\u00ae'
 }
 
@@ -152,48 +159,60 @@
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u00fe'
     shift+ralt, capslock+ralt:          '\u00de'
+    shift+capslock+ralt:                '\u00fe'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
     ralt:                               '\u00fc'
     shift+ralt, capslock+ralt:          '\u00dc'
+    shift+capslock+ralt:                '\u00fc'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u00fa'
     shift+ralt, capslock+ralt:          '\u00da'
+    shift+capslock+ralt:                '\u00fa'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ed'
     shift+ralt, capslock+ralt:          '\u00cd'
+    shift+capslock+ralt:                '\u00ed'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f3'
     shift+ralt, capslock+ralt:          '\u00d3'
+    shift+capslock+ralt:                '\u00f3'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\u00f6'
     shift+ralt, capslock+ralt:          '\u00d6'
+    shift+capslock+ralt:                '\u00f6'
 }
 
 key LEFT_BRACKET {
@@ -224,14 +243,17 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    shift+ralt, ralt+capslock:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u00df'
     shift+ralt:                         '\u00a7'
 }
@@ -240,46 +262,55 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u00f0'
     shift+ralt, capslock+ralt:          '\u00d0'
+    shift+capslock+ralt:                '\u00f0'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u00f8'
     shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key SEMICOLON {
@@ -312,20 +343,24 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u00e6'
     shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u00a9'
     shift+ralt:                         '\u00a2'
 }
@@ -334,26 +369,31 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u00f1'
     shift+ralt, capslock+ralt:          '\u00d1'
+    shift+capslock+ralt:                '\u00f1'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
@@ -363,6 +403,7 @@
     shift:                              '<'
     ralt:                               '\u00e7'
     shift+ralt, capslock+ralt:          '\u00c7'
+    shift+capslock+ralt:                '\u00e7'
 }
 
 key PERIOD {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
index fe82c8d..713afba 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
@@ -129,60 +129,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
     shift, capslock:                    ':'
+    shift+capslock:                     ':'
 }
 
 key LEFT_BRACKET {
@@ -209,48 +219,56 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key O {
@@ -263,6 +281,7 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key APOSTROPHE {
@@ -277,42 +296,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm b/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
index ef545b8..27a03da 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
@@ -116,18 +116,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -135,54 +138,63 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key RIGHT_BRACKET {
     label:                              '\u00d5'
     base:                               '\u00f5'
     shift, capslock:                    '\u00d5'
+    shift+capslock:                     '\u00f5'
     ralt:                               '\u00a7'
 }
 
@@ -192,68 +204,80 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+shift, ralt+capslock:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
     ralt:                               '\u0302'
 }
 
@@ -277,44 +301,52 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+shift, ralt+capslock:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm b/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
index b4deed4..79096ad 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u00f8'
-    ralt+capslock, shift+ralt:          '\u00d8'
+    shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
     ralt:                               '\u00e6'
-    ralt+capslock, shift+ralt:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key BACKSLASH {
@@ -299,53 +333,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 89e83da..4906304 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -123,18 +123,21 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -142,42 +145,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -199,60 +209,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key APOSTROPHE {
@@ -279,36 +299,42 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
index 55ddd60..03b5c19 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
@@ -119,54 +119,63 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00a7'
 }
 
@@ -174,6 +183,7 @@
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\u00b6'
 }
 
@@ -196,54 +206,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -279,42 +298,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
@@ -335,5 +361,6 @@
     label:                              '\u00c9'
     base:                               '\u00e9'
     shift, capslock:                    '\u00c9'
+    shift+capslock:                     '\u00e9'
     ralt:                               '\u0301'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm b/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
index 35b66a3..a8f229f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
@@ -28,6 +28,7 @@
     label:                              '\u201e'
     base:                               '\u201e'
     shift, capslock:                    '\u201c'
+    shift+capslock:                     '\u201e'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -128,79 +129,92 @@
     label:                              '\u10e5'
     base:                               '\u10e5'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u10ec'
     base:                               '\u10ec'
     shift, capslock:                    '\u10ed'
+    shift+capslock:                     '\u10ec'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u10d4'
     base:                               '\u10d4'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u10e0'
     base:                               '\u10e0'
     shift, capslock:                    '\u10e6'
+    shift+capslock:                     '\u10e0'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u10e2'
     base:                               '\u10e2'
     shift, capslock:                    '\u10d7'
+    shift+capslock:                     '\u10e2'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u10e7'
     base:                               '\u10e7'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u10e3'
     base:                               '\u10e3'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u10d8'
     base:                               '\u10d8'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u10dd'
     base:                               '\u10dd'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u10de'
     base:                               '\u10de'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '['
     base:                               '['
-    shift, capslock:                    '{'
+    shift:                              '{'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -208,7 +222,7 @@
 key RIGHT_BRACKET {
     label:                              ']'
     base:                               ']'
-    shift, capslock:                    '}'
+    shift:                              '}'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -227,72 +241,84 @@
     label:                              '\u10d0'
     base:                               '\u10d0'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u10e1'
     base:                               '\u10e1'
     shift, capslock:                    '\u10e8'
+    shift+capslock:                     '\u10e1'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u10d3'
     base:                               '\u10d3'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u10e4'
     base:                               '\u10e4'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u10d2'
     base:                               '\u10d2'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u10f0'
     base:                               '\u10f0'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u10ef'
     base:                               '\u10ef'
     shift, capslock:                    '\u10df'
+    shift+capslock:                     '\u10ef'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u10d9'
     base:                               '\u10d9'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u10da'
     base:                               '\u10da'
     shift, capslock:                    '\u20be'
+    shift+capslock:                     '\u10da'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
-    shift, capslock:                    ':'
+    shift:                              ':'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -300,7 +326,7 @@
 key APOSTROPHE {
     label:                              '\''
     base:                               '\''
-    shift, capslock:                    '"'
+    shift:                              '"'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -311,57 +337,66 @@
     label:                              '\u10d6'
     base:                               '\u10d6'
     shift, capslock:                    '\u10eb'
+    shift+capslock:                     '\u10d6'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u10ee'
     base:                               '\u10ee'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u10ea'
     base:                               '\u10ea'
     shift, capslock:                    '\u10e9'
+    shift+capslock:                     '\u10ea'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u10d5'
     base:                               '\u10d5'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u10d1'
     base:                               '\u10d1'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u10dc'
     base:                               '\u10dc'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u10db'
     base:                               '\u10db'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              ','
     base:                               ','
-    shift, capslock:                    '<'
+    shift:                              '<'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -369,7 +404,7 @@
 key PERIOD {
     label:                              '.'
     base:                               '.'
-    shift, capslock:                    '>'
+    shift:                              '>'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
index d9caa32..23ccc9a 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
@@ -18,7 +18,7 @@
 
 type OVERLAY
 
-map key 12 SLASH            # § ? \
+map key 12 SLASH            # § ? \
 map key 21 Z
 map key 44 Y
 map key 53 MINUS            # - _
@@ -117,6 +117,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -124,12 +125,14 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -137,48 +140,56 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key RIGHT_BRACKET {
@@ -194,66 +205,77 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
 }
 
 key BACKSLASH {
@@ -275,42 +297,49 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_greek.kcm b/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
index a7684e1..6eff114 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
@@ -24,88 +24,88 @@
 
 key GRAVE {
     label:                              '`'
-    base, capslock:                     '`'
+    base:                               '`'
     shift:                              '~'
 }
 
 key 1 {
     label:                              '1'
-    base, capslock:                     '1'
+    base:                               '1'
     shift:                              '!'
 }
 
 key 2 {
     label:                              '2'
-    base, capslock:                     '2'
+    base:                               '2'
     shift:                              '@'
     ralt:                               '\u00b2'
 }
 
 key 3 {
     label:                              '3'
-    base, capslock:                     '3'
+    base:                               '3'
     shift:                              '#'
     ralt:                               '\u00b3'
 }
 
 key 4 {
     label:                              '4'
-    base, capslock:                     '4'
+    base:                               '4'
     shift:                              '$'
     ralt:                               '\u00a3'
 }
 
 key 5 {
     label:                              '5'
-    base, capslock:                     '5'
+    base:                               '5'
     shift:                              '%'
     ralt:                               '\u00a7'
 }
 
 key 6 {
     label:                              '6'
-    base, capslock:                     '6'
+    base:                               '6'
     shift:                              '^'
     ralt:                               '\u00b6'
 }
 
 key 7 {
     label:                              '7'
-    base, capslock:                     '7'
+    base:                               '7'
     shift:                              '&'
 }
 
 key 8 {
     label:                              '8'
-    base, capslock:                     '8'
+    base:                               '8'
     shift:                              '*'
     ralt:                               '\u00a4'
 }
 
 key 9 {
     label:                              '9'
-    base, capslock:                     '9'
+    base:                               '9'
     shift:                              '('
     ralt:                               '\u00a6'
 }
 
 key 0 {
     label:                              '0'
-    base, capslock:                     '0'
+    base:                               '0'
     shift:                              ')'
     ralt:                               '\u00b0'
 }
 
 key MINUS {
     label:                              '-'
-    base, capslock:                     '-'
+    base:                               '-'
     shift:                              '_'
     ralt:                               '\u00b1'
 }
 
 key EQUALS {
     label:                              '='
-    base, capslock:                     '='
+    base:                               '='
     shift:                              '+'
     ralt:                               '\u00bd'
 }
@@ -114,13 +114,13 @@
 
 key Q {
     label:                              'Q'
-    base, capslock:                     ';'
+    base:                               ';'
     shift:                              ':'
 }
 
 key W {
     label:                              'W'
-    base, capslock:                     '\u03c2'
+    base:                               '\u03c2'
     shift:                              '\u0385'
 }
 
@@ -128,6 +128,7 @@
     label:                              'E'
     base:                               '\u03b5'
     shift, capslock:                    '\u0395'
+    shift+capslock:                     '\u03b5'
     ralt:                               '\u20ac'
 }
 
@@ -135,6 +136,7 @@
     label:                              'R'
     base:                               '\u03c1'
     shift, capslock:                    '\u03a1'
+    shift+capslock:                     '\u03c1'
     ralt:                               '\u00ae'
 }
 
@@ -142,12 +144,14 @@
     label:                              'T'
     base:                               '\u03c4'
     shift, capslock:                    '\u03a4'
+    shift+capslock:                     '\u03c4'
 }
 
 key Y {
     label:                              'Y'
     base:                               '\u03c5'
     shift, capslock:                    '\u03a5'
+    shift+capslock:                     '\u03c5'
     ralt:                               '\u00a5'
 }
 
@@ -155,36 +159,40 @@
     label:                              'U'
     base:                               '\u03b8'
     shift, capslock:                    '\u0398'
+    shift+capslock:                     '\u03b8'
 }
 
 key I {
     label:                              'I'
     base:                               '\u03b9'
     shift, capslock:                    '\u0399'
+    shift+capslock:                     '\u03b9'
 }
 
 key O {
     label:                              'O'
     base:                               '\u03bf'
     shift, capslock:                    '\u039f'
+    shift+capslock:                     '\u03bf'
 }
 
 key P {
     label:                              'P'
     base:                               '\u03c0'
     shift, capslock:                    '\u03a0'
+    shift+capslock:                     '\u03c0'
 }
 
 key LEFT_BRACKET {
     label:                              '['
-    base, capslock:                     '['
+    base:                               '['
     shift:                              '{'
     ralt:                               '\u00ab'
 }
 
 key RIGHT_BRACKET {
     label:                              ']'
-    base, capslock:                     ']'
+    base:                               ']'
     shift:                              '}'
     ralt:                               '\u00bb'
 }
@@ -195,59 +203,68 @@
     label:                              'A'
     base:                               '\u03b1'
     shift, capslock:                    '\u0391'
+    shift+capslock:                     '\u03b1'
 }
 
 key S {
     label:                              'S'
     base:                               '\u03c3'
     shift, capslock:                    '\u03a3'
+    shift+capslock:                     '\u03c3'
 }
 
 key D {
     label:                              'D'
     base:                               '\u03b4'
     shift, capslock:                    '\u0394'
+    shift+capslock:                     '\u03b4'
 }
 
 key F {
     label:                              'F'
     base:                               '\u03c6'
     shift, capslock:                    '\u03a6'
+    shift+capslock:                     '\u03c6'
 }
 
 key G {
     label:                              'G'
     base:                               '\u03b3'
     shift, capslock:                    '\u0393'
+    shift+capslock:                     '\u03b3'
 }
 
 key H {
     label:                              'H'
     base:                               '\u03b7'
     shift, capslock:                    '\u0397'
+    shift+capslock:                     '\u03b7'
 }
 
 key J {
     label:                              'J'
     base:                               '\u03be'
     shift, capslock:                    '\u039e'
+    shift+capslock:                     '\u03be'
 }
 
 key K {
     label:                              'K'
     base:                               '\u03ba'
     shift, capslock:                    '\u039a'
+    shift+capslock:                     '\u03ba'
 }
 
 key L {
     label:                              'L'
     base:                               '\u03bb'
     shift, capslock:                    '\u039b'
+    shift+capslock:                     '\u03bb'
 }
 
 key SEMICOLON {
     label:                              ';'
-    base, capslock:                     '\u0301'
+    base:                               '\u0301'
 #should be \u0384 (greek tonos)
     shift:                              '\u0308'
     ralt:                               '\u0385'
@@ -255,13 +272,13 @@
 
 key APOSTROPHE {
     label:                              '\''
-    base, capslock:                     '\''
+    base:                               '\''
     shift:                              '"'
 }
 
 key BACKSLASH {
     label:                              '\\'
-    base, capslock:                     '\\'
+    base:                               '\\'
     shift:                              '|'
     ralt:                               '\u00ac'
 }
@@ -270,7 +287,7 @@
 
 key PLUS {
     label:                              '<'
-    base, capslock:                     '<'
+    base:                               '<'
     shift:                              '>'
     ralt:                               '\\'
     shift+ralt:                         '|'
@@ -280,18 +297,21 @@
     label:                              'Z'
     base:                               '\u03b6'
     shift, capslock:                    '\u0396'
+    shift+capslock:                     '\u03b6'
 }
 
 key X {
     label:                              'X'
     base:                               '\u03c7'
     shift, capslock:                    '\u03a7'
+    shift+capslock:                     '\u03c7'
 }
 
 key C {
     label:                              'C'
     base:                               '\u03c8'
     shift, capslock:                    '\u03a8'
+    shift+capslock:                     '\u03c8'
     ralt:                               '\u00a9'
 }
 
@@ -299,40 +319,44 @@
     label:                              'V'
     base:                               '\u03c9'
     shift, capslock:                    '\u03a9'
+    shift+capslock:                     '\u03c9'
 }
 
 key B {
     label:                              'B'
     base:                               '\u03b2'
     shift, capslock:                    '\u0392'
+    shift+capslock:                     '\u03b2'
 }
 
 key N {
     label:                              'N'
     base:                               '\u03bd'
     shift, capslock:                    '\u039d'
+    shift+capslock:                     '\u03bd'
 }
 
 key M {
     label:                              'M'
     base:                               '\u03bc'
     shift, capslock:                    '\u039c'
+    shift+capslock:                     '\u03bc'
 }
 
 key COMMA {
     label:                              ','
-    base, capslock:                     ','
+    base:                               ','
     shift:                              '<'
 }
 
 key PERIOD {
     label:                              '.'
-    base, capslock:                     '.'
+    base:                               '.'
     shift:                              '>'
 }
 
 key SLASH {
     label:                              '/'
-    base, capslock:                     '/'
+    base:                               '/'
     shift:                              '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm b/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
index 283cb4e..11ade42 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
@@ -121,18 +121,21 @@
     label:                              'Q'
     base:                               '/'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               '\u0027'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               '\u05e7'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -140,24 +143,28 @@
     label:                              'R'
     base:                               '\u05e8'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               '\u05d0'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               '\u05d8'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               '\u05d5'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u05f0'
 }
 
@@ -165,29 +172,32 @@
     label:                              'I'
     base:                               '\u05df'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               '\u05dd'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               '\u05e4'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              ']'
-    base, capslock:                     ']'
+    base:                               ']'
     shift:                              '}'
 }
 
 key RIGHT_BRACKET {
     label:                              '['
-    base, capslock:                     '['
+    base:                               '['
     shift:                              '{'
 }
 
@@ -197,36 +207,42 @@
     label:                              'A'
     base:                               '\u05e9'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               '\u05d3'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               '\u05d2'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               '\u05db'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               '\u05e2'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               '\u05d9'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u05f2'
 }
 
@@ -234,6 +250,7 @@
     label:                              'J'
     base:                               '\u05d7'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
     ralt:                               '\u05f1'
 }
 
@@ -241,12 +258,14 @@
     label:                              'K'
     base:                               '\u05dc'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               '\u05da'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -254,6 +273,7 @@
     base:                               '\u05e3'
     shift:                              ':'
     capslock:                           ';'
+    shift+capslock:                     ':'
 }
 
 key APOSTROPHE {
@@ -261,6 +281,7 @@
     base:                               ','
     shift:                              '"'
     capslock:                           '\''
+    shift+capslock:                     '"'
 }
 
 key BACKSLASH {
@@ -273,7 +294,7 @@
 
 key PLUS {
     label:                              '\\'
-    base, capslock:                     '\\'
+    base:                               '\\'
     shift:                              '|'
 }
 
@@ -281,42 +302,49 @@
     label:                              'Z'
     base:                               '\u05d6'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               '\u05e1'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               '\u05d1'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               '\u05d4'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               '\u05e0'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               '\u05de'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               '\u05e6'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
@@ -324,6 +352,7 @@
     base:                               '\u05ea'
     shift:                              '>'
     capslock:                           ','
+    shift+capslock:                     '>'
 }
 
 key PERIOD {
@@ -331,6 +360,7 @@
     base:                               '\u05e5'
     shift:                              '<'
     capslock:                           '.'
+    shift+capslock:                     '<'
 }
 
 key SLASH {
@@ -338,4 +368,5 @@
     base:                               '.'
     shift:                              '?'
     capslock:                           '/'
+    shift+capslock:                     '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
index dafb50b..6c947c7 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
@@ -101,6 +101,7 @@
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u030b'
 }
 
@@ -108,6 +109,7 @@
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
     ralt:                               '\u0308'
 }
 
@@ -115,6 +117,7 @@
     label:                              '\u00d3'
     base:                               '\u00f3'
     shift, capslock:                    '\u00d3'
+    shift+capslock:                     '\u00f3'
     ralt:                               '\u0327'
 }
 
@@ -124,6 +127,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\\'
 }
 
@@ -131,6 +135,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '|'
 }
 
@@ -138,6 +143,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u00c4'
 }
 
@@ -145,24 +151,28 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u20ac'
 }
 
@@ -170,6 +180,7 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00cd'
 }
 
@@ -177,18 +188,21 @@
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0150'
     base:                               '\u0151'
     shift, capslock:                    '\u0150'
+    shift+capslock:                     '\u0151'
     ralt:                               '\u00f7'
 }
 
@@ -196,6 +210,7 @@
     label:                              '\u00da'
     base:                               '\u00fa'
     shift, capslock:                    '\u00da'
+    shift+capslock:                     '\u00fa'
     ralt:                               '\u00d7'
 }
 
@@ -205,6 +220,7 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e4'
 }
 
@@ -212,6 +228,7 @@
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0111'
 }
 
@@ -219,6 +236,7 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0110'
 }
 
@@ -226,6 +244,7 @@
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '['
 }
 
@@ -233,6 +252,7 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               ']'
 }
 
@@ -240,12 +260,14 @@
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
     ralt:                               '\u00ed'
 }
 
@@ -253,6 +275,7 @@
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0197'
 }
 
@@ -260,6 +283,7 @@
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0141'
 }
 
@@ -267,6 +291,7 @@
     label:                              '\u00c9'
     base:                               '\u00e9'
     shift, capslock:                    '\u00c9'
+    shift+capslock:                     '\u00e9'
     ralt:                               '$'
 }
 
@@ -274,6 +299,7 @@
     label:                              '\u00c1'
     base:                               '\u00e1'
     shift, capslock:                    '\u00c1'
+    shift+capslock:                     '\u00e1'
     ralt:                               '\u00df'
 }
 
@@ -281,6 +307,7 @@
     label:                              '\u0170'
     base:                               '\u0171'
     shift, capslock:                    '\u0170'
+    shift+capslock:                     '\u0171'
     ralt:                               '\u00a4'
 }
 
@@ -290,6 +317,7 @@
     label:                              '\u00cd'
     base:                               '\u00ed'
     shift, capslock:                    '\u00cd'
+    shift+capslock:                     '\u00ed'
     ralt:                               '<'
 }
 
@@ -297,6 +325,7 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
     ralt:                               '>'
 }
 
@@ -304,6 +333,7 @@
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
     ralt:                               '#'
 }
 
@@ -311,6 +341,7 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '&'
 }
 
@@ -318,6 +349,7 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -325,6 +357,7 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '{'
 }
 
@@ -332,6 +365,7 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '}'
 }
 
@@ -339,6 +373,7 @@
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm b/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
index 117f58b..5131b4f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
@@ -99,6 +99,7 @@
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\\'
 }
 
@@ -114,6 +115,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -121,12 +123,14 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -134,48 +138,56 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0110'
     base:                               '\u0111'
     shift, capslock:                    '\u0110'
+    shift+capslock:                     '\u0111'
 }
 
 key RIGHT_BRACKET {
@@ -191,60 +203,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c6'
     base:                               '\u00e6'
     shift, capslock:                    '\u00c6'
+    shift+capslock:                     '\u00e6'
 }
 
 key APOSTROPHE {
@@ -274,42 +296,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
@@ -329,4 +358,5 @@
     label:                              '\u00de'
     base:                               '\u00fe'
     shift, capslock:                    '\u00de'
+    shift+capslock:                     '\u00fe'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_italian.kcm b/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
index bd2d25a..309d8b2 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
@@ -109,18 +109,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -128,42 +131,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -188,54 +198,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -270,42 +289,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
index d4bc0c0..3b77cb1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
@@ -119,70 +119,85 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u0113'
-    shift+ralt, ralt+capslock:          '\u0112'
+    shift+ralt, capslock+ralt:          '\u0112'
+    shift+capslock+ralt:                '\u0113'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
     ralt:                               '\u0157'
-    shift+ralt, ralt+capslock:          '\u0156'
+    shift+ralt, capslock+ralt:          '\u0156'
+    shift+capslock+ralt:                '\u0157'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u016b'
-    shift+ralt, ralt+capslock:          '\u016a'
+    shift+ralt, capslock+ralt:          '\u016a'
+    shift+capslock+ralt:                '\u016b'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u012b'
-    shift+ralt, ralt+capslock:          '\u012a'
+    shift+ralt, capslock+ralt:          '\u012a'
+    shift+capslock+ralt:                '\u012b'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    shift+ralt, ralt+capslock:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -204,64 +219,78 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u0101'
-    shift+ralt, ralt+capslock:          '\u0100'
+    shift+ralt, capslock+ralt:          '\u0100'
+    shift+capslock+ralt:                '\u0101'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    shift+ralt, ralt+capslock:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u0123'
-    shift+ralt, ralt+capslock:          '\u0122'
+    shift+ralt, capslock+ralt:          '\u0122'
+    shift+capslock+ralt:                '\u0123'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0137'
-    shift+ralt, ralt+capslock:          '\u0136'
+    shift+ralt, capslock+ralt:          '\u0136'
+    shift+capslock+ralt:                '\u0137'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u013c'
-    shift+ralt, ralt+capslock:          '\u013b'
+    shift+ralt, capslock+ralt:          '\u013b'
+    shift+capslock+ralt:                '\u013c'
 }
 
 key SEMICOLON {
@@ -298,48 +327,58 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    shift+ralt, ralt+capslock:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    shift+ralt, ralt+capslock:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u0146'
-    shift+ralt, ralt+capslock:          '\u0145'
+    shift+ralt, capslock+ralt:          '\u0145'
+    shift+capslock+ralt:                '\u0146'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm b/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
index 72ca333..bcfdb12 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
@@ -32,6 +32,7 @@
     label:                              '1'
     base:                               '\u0105'
     shift, capslock:                    '\u0104'
+    shift+capslock:                     '\u0105'
     ralt:                               '1'
     shift+ralt:                         '!'
 }
@@ -40,6 +41,7 @@
     label:                              '2'
     base:                               '\u010d'
     shift, capslock:                    '\u010c'
+    shift+capslock:                     '\u010d'
     ralt:                               '2'
     shift+ralt:                         '@'
 }
@@ -48,6 +50,7 @@
     label:                              '3'
     base:                               '\u0119'
     shift, capslock:                    '\u0118'
+    shift+capslock:                     '\u0119'
     ralt:                               '3'
     shift+ralt:                         '#'
 }
@@ -56,6 +59,7 @@
     label:                              '4'
     base:                               '\u0117'
     shift, capslock:                    '\u0116'
+    shift+capslock:                     '\u0117'
     ralt:                               '4'
     shift+ralt:                         '$'
 }
@@ -64,6 +68,7 @@
     label:                              '5'
     base:                               '\u012f'
     shift, capslock:                    '\u012e'
+    shift+capslock:                     '\u012f'
     ralt:                               '5'
     shift+ralt:                         '%'
 }
@@ -72,6 +77,7 @@
     label:                              '6'
     base:                               '\u0161'
     shift, capslock:                    '\u0160'
+    shift+capslock:                     '\u0161'
     ralt:                               '6'
     shift+ralt:                         '\u0302'
 }
@@ -80,6 +86,7 @@
     label:                              '7'
     base:                               '\u0173'
     shift, capslock:                    '\u0172'
+    shift+capslock:                     '\u0173'
     ralt:                               '7'
     shift+ralt:                         '&'
 }
@@ -88,6 +95,7 @@
     label:                              '8'
     base:                               '\u016b'
     shift, capslock:                    '\u016a'
+    shift+capslock:                     '\u016b'
     ralt:                               '8'
     shift+ralt:                         '*'
 }
@@ -116,6 +124,7 @@
     label:                              '='
     base:                               '\u017e'
     shift, capslock:                    '\u017d'
+    shift+capslock:                     '\u017e'
     ralt:                               '='
     shift+ralt:                         '+'
 }
@@ -126,18 +135,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -145,42 +157,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -201,54 +220,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -281,42 +309,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm b/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
index 3d4a8c6..77cc672 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
@@ -28,6 +28,7 @@
     label:                              '='
     base:                               '='
     shift, capslock:                    '+'
+    shift+capslock:                     '+'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -122,86 +123,107 @@
     label:                              '\u0444'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0446'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0443'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u0436'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u044d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u043d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0433'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0448'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u04af'
     base:                               '\u04af'
     shift, capslock:                    '\u04ae'
+    shift+capslock:                     '\u04af'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0437'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u043a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -210,6 +232,7 @@
     label:                              '\u044a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -220,78 +243,97 @@
     label:                              '\u0439'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u044b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0431'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u04e9'
     base:                               '\u04e9'
     shift, capslock:                    '\u04e8'
+    shift+capslock:                     '\u04e9'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u0430'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0445'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u0440'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u043e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u043b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0434'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -300,6 +342,7 @@
     label:                              '\u043f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -318,62 +361,77 @@
     label:                              '\u044f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0447'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0451'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u0441'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u043c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0438'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u0442'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u044c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -382,6 +440,7 @@
     label:                              '\u0432'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm b/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
index 560dd16..cae1c94 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d8'
     base:                               '\u00f8'
     shift, capslock:                    '\u00d8'
+    shift+capslock:                     '\u00f8'
     ralt:                               '\u00f6'
-    ralt+capslock, shift+ralt:          '\u00d6'
+    shift+ralt, capslock+ralt:          '\u00d6'
+    shift+capslock+ralt:                '\u00f6'
 }
 
 key APOSTROPHE {
     label:                              '\u00c6'
     base:                               '\u00e6'
     shift, capslock:                    '\u00c6'
+    shift+capslock:                     '\u00e6'
     ralt:                               '\u00e4'
-    ralt+capslock, shift+ralt:          '\u00c4'
+    shift+ralt, capslock+ralt:          '\u00c4'
+    shift+capslock+ralt:                '\u00e4'
 }
 
 key BACKSLASH {
@@ -298,53 +332,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
index bfe7821..6744922 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
@@ -22,231 +22,222 @@
     label:                              '\u0634'
     base:                               '\u0634'
     shift, capslock:                    '\u0624'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0634'
 }
 
 key B {
     label:                              '\u0630'
     base:                               '\u0630'
     shift, capslock:                    '\u200C'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0630'
 }
 
 key C {
     label:                              '\u0632'
     base:                               '\u0632'
     shift, capslock:                    '\u0698'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0632'
 }
 
 key D {
     label:                              '\u06CC'
     base:                               '\u06CC'
     shift, capslock:                    '\u064A'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u06CC'
 }
 
 key E {
     label:                              '\u062B'
     base:                               '\u062B'
     shift, capslock:                    '\u064D'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u062B'
 }
 
 key F {
     label:                              '\u0628'
     base:                               '\u0628'
     shift, capslock:                    '\u0625'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0628'
 }
 
 key G {
     label:                              '\u0644'
     base:                               '\u0644'
     shift, capslock:                    '\u0623'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0644'
 }
 
 key H {
     label:                              '\u0627'
     base:                               '\u0627'
     shift, capslock:                    '\u0622'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0627'
 }
 
 key I {
     label:                              '\u0647'
     base:                               '\u0647'
     shift, capslock:                    '\u0651'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0647'
 }
 
 key J {
     label:                              '\u062A'
     base:                               '\u062A'
     shift, capslock:                    '\u0629'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u062A'
 }
 
 key K {
     label:                              '\u0646'
     base:                               '\u0646'
     shift, capslock:                    '\u00AB'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0646'
 }
 
 key L {
     label:                              '\u0645'
     base:                               '\u0645'
     shift, capslock:                    '\u00BB'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0645'
 }
 
 key M {
     label:                              '\u067E'
     base:                               '\u067E'
     shift, capslock:                    '\u0621'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u067E'
 }
 
 key N {
     label:                              '\u062F'
     base:                               '\u062F'
     shift, capslock:                    '\u0654'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u062F'
 }
 
 key O {
     label:                              '\u062E'
     base:                               '\u062E'
-    shift, capslock:                    ']'
-    ctrl, alt, meta:                    none
+    shift:                              ']'
 }
 
 key P {
     label:                              '\u062D'
     base:                               '\u062D'
-    shift, capslock:                    '['
-    ctrl, alt, meta:                    none
+    shift:                              '['
 }
 
 key Q {
     label:                              '\u0636'
     base:                               '\u0636'
     shift, capslock:                    '\u0652'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0636'
 }
 
 key R {
     label:                              '\u0642'
     base:                               '\u0642'
     shift, capslock:                    '\u064B'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0642'
 }
 
 key S {
     label:                              '\u0633'
     base:                               '\u0633'
     shift, capslock:                    '\u0626'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0633'
 }
 
 key T {
     label:                              '\u0641'
     base:                               '\u0641'
     shift, capslock:                    '\u064F'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0641'
 }
 
 key U {
     label:                              '\u0639'
     base:                               '\u0639'
     shift, capslock:                    '\u064E'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0639'
 }
 
 key V {
     label:                              '\u0631'
     base:                               '\u0631'
     shift, capslock:                    '\u0670'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0631'
 }
 
 key W {
     label:                              '\u0635'
     base:                               '\u0635'
     shift, capslock:                    '\u064C'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0635'
 }
 
 key X {
     label:                              '\u0637'
     base:                               '\u0637'
     shift, capslock:                    '\u0653'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0637'
 }
 
 key Y {
     label:                              '\u063A'
     base:                               '\u063A'
     shift, capslock:                    '\u0650'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u063A'
 }
 
 key Z {
     label:                              '\u0638'
     base:                               '\u0638'
     shift, capslock:                    '\u0643'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0638'
 }
 
 key 0 {
     label, number:                      '\u06F0'
     base:                               '\u06F0'
     shift:                              '('
-    ctrl, alt, meta:                    none
 }
 
 key 1 {
     label, number:                      '\u06F1'
     base:                               '\u06F1'
     shift:                              '!'
-    ctrl, alt, meta:                    none
 }
 
 key 2 {
     label, number:                      '\u06F2'
     base:                               '\u06F2'
     shift:                              '\u066C'
-    ctrl, alt, meta:                    none
 
 }
 key 3 {
     label, number:                      '\u06F3'
     base:                               '\u06F3'
     shift:                              '\u066B'
-    ctrl, alt, meta:                    none
 }
 
 key 4 {
     label, number:                      '\u06F4'
     base:                               '\u06F4'
     shift:                              '\uFDFC'
-    ctrl, alt, meta:                    none
 }
 
 key 5 {
     label, number:                      '\u06F5'
     base:                               '\u06F5'
     shift:                              '\u066A'
-    ctrl, alt, meta:                    none
 }
 
 key 6 {
     label, number:                      '\u06F6'
     base:                               '\u06F6'
     shift:                              '\u00D7'
-    ctrl, alt, meta:                    none
 }
 
 
@@ -254,248 +245,82 @@
     label, number:                      '\u06F7'
     base:                               '\u06F7'
     shift:                              '\u060C'
-    ctrl, alt, meta:                    none
 }
 
 key 8 {
     label, number:                      '\u06F8'
     base:                               '\u06F8'
     shift:                              '*'
-    ctrl, alt, meta:                    none
 }
 
 key 9 {
     label, number:                      '\u06F9'
     base:                               '\u06F9'
     shift:                              ')'
-    ctrl, alt, meta:                    none
-}
-
-key SPACE {
-    label:                              ' '
-    base:                               ' '
-    ctrl, alt, meta:                    none
-}
-
-key ENTER {
-    label:                              '\n'
-    base:                               '\n'
-    ctrl, alt, meta:                    none
-}
-
-key TAB {
-    label:                              '\t'
-    base:                               '\t'
-    ctrl, alt, meta:                    none
 }
 
 key COMMA {
     label, number:                      '\u0648'
     base:                               '\u0648'
     shift:                              '>'
-    ctrl, alt, meta:                    none
 }
 
 key PERIOD {
     label, number:                      '.'
     base:                               '.'
     shift:                              '<'
-    ctrl, alt, meta:                    none
 }
 
 key SLASH {
     label, number:                      '/'
     base:                               '/'
     shift:                              '\u061F'
-    ctrl, alt, meta:                    none
 }
 
 key GRAVE {
     label, number:                      '`'
     base:                               '`'
     shift:                              '\u00F7'
-    ctrl, alt, meta:                    none
 }
 
-
 key MINUS {
     label, number:                      '-'
     base:                               '-'
     shift:                              '_'
-    ctrl, alt, meta:                    none
 }
 
 key EQUALS {
     label, number:                      '='
     base:                               '='
     shift:                              '+'
-    ctrl, alt, meta:                    none
 }
 
 key LEFT_BRACKET {
     label, number:                      '\u062C'
     base:                               '\u062C'
     shift:                              '}'
-    ctrl, alt, meta:                    none
 }
 
 key RIGHT_BRACKET {
     label, number:                      '\u0686'
     base:                               '\u0686'
     shift:                              '{'
-    ctrl, alt, meta:                    none
 }
 
 key BACKSLASH {
     label, number:                      '\\'
     base:                               '\\'
     shift:                              '|'
-    ctrl, alt, meta:                    none
 }
 
 key SEMICOLON {
     label, number:                      '\u06A9'
     base:                               '\u06A9'
     shift:                              ':'
-    ctrl, alt, meta:                    none
 }
 
 key APOSTROPHE {
     label, number:                      '\''
     base:                               '\''
     shift:                              '\"'
-    ctrl, alt, meta:                    none
-}
-
-### Numeric keypad ###
-
-key NUMPAD_0 {
-    label, number:                      '0'
-    base:                               fallback INSERT
-    numlock:                            '0'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_1 {
-    label, number:                      '1'
-    base:                               fallback MOVE_END
-    numlock:                            '1'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_2 {
-    label, number:                      '2'
-    base:                               fallback DPAD_DOWN
-    numlock:                            '2'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_3 {
-    label, number:                      '3'
-    base:                               fallback PAGE_DOWN
-    numlock:                            '3'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_4 {
-    label, number:                      '4'
-    base:                               fallback DPAD_LEFT
-    numlock:                            '4'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_5 {
-    label, number:                      '5'
-    base:                               fallback DPAD_CENTER
-    numlock:                            '5'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_6 {
-    label, number:                      '6'
-    base:                               fallback DPAD_RIGHT
-    numlock:                            '6'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_7 {
-    label, number:                      '7'
-    base:                               fallback MOVE_HOME
-    numlock:                            '7'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_8 {
-    label, number:                      '8'
-    base:                               fallback DPAD_UP
-    numlock:                            '8'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_9 {
-    label, number:                      '9'
-    base:                               fallback PAGE_UP
-    numlock:                            '9'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_LEFT_PAREN {
-    label, number:                      ')'
-    base:                               ')'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_RIGHT_PAREN {
-    label, number:                      '('
-    base:                               '('
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_DIVIDE {
-    label, number:                      '/'
-    base:                               '/'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_MULTIPLY {
-    label, number:                      '*'
-    base:                               '*'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_SUBTRACT {
-    label, number:                      '-'
-    base:                               '-'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_ADD {
-    label, number:                      '+'
-    base:                               '+'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_DOT {
-    label, number:                      '.'
-    base:                               fallback FORWARD_DEL
-    numlock:                            '.'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_COMMA {
-    label, number:                      ','
-    base:                               ','
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_EQUALS {
-    label, number:                      '='
-    base:                               '='
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_ENTER {
-    label:                              '\n'
-    base:                               '\n' fallback ENTER
-    ctrl, alt, meta:                    none fallback ENTER
-}
+}
\ No newline at end of file
diff --git a/packages/InputDevices/res/raw/keyboard_layout_polish.kcm b/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
index 559ec07..66fbefc1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
@@ -104,64 +104,76 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u0119'
-    ralt+shift, ralt+capslock:          '\u0118'
+    shift+ralt, capslock+ralt:          '\u0118'
+    shift+capslock+ralt:                '\u0119'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00F3'
-    ralt+shift, ralt+capslock:          '\u00D3'
+    shift+ralt, capslock+ralt:          '\u00D3'
+    shift+capslock+ralt:                '\u00F3'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -188,60 +200,72 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u0105'
-    ralt+shift, ralt+capslock:          '\u0104'
+    shift+ralt, capslock+ralt:          '\u0104'
+    shift+capslock+ralt:                '\u0105'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u015b'
-    ralt+shift, ralt+capslock:          '\u015a'
+    shift+ralt, capslock+ralt:          '\u015a'
+    shift+capslock+ralt:                '\u015b'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0142'
-    ralt+shift, ralt+capslock:          '\u0141'
+    shift+ralt, capslock+ralt:          '\u0141'
+    shift+capslock+ralt:                '\u0142'
 }
 
 key SEMICOLON {
@@ -262,50 +286,61 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017c'
-    ralt+shift, ralt+capslock:          '\u017b'
+    shift+ralt, capslock+ralt:          '\u017b'
+    shift+capslock+ralt:                '\u017c'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
     ralt:                               '\u017a'
-    ralt+shift, ralt+capslock:          '\u0179'
+    shift+ralt, capslock+ralt:          '\u0179'
+    shift+capslock+ralt:                '\u017a'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u0107'
-    ralt+shift, ralt+capslock:          '\u0106'
+    shift+ralt, capslock+ralt:          '\u0106'
+    shift+capslock+ralt:                '\u0107'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u0144'
-    ralt+shift, ralt+capslock:          '\u0143'
+    shift+ralt, capslock+ralt:          '\u0143'
+    shift+capslock+ralt:                '\u0144'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm b/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
index 47ee867..6fe0e47 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
@@ -115,18 +115,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -134,42 +137,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -191,60 +201,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key APOSTROPHE {
@@ -272,42 +292,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_russian.kcm b/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
index 41c6bb3..ecada49 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
@@ -28,6 +28,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -124,86 +125,107 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -212,6 +234,7 @@
     label:                              '\u042a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -222,78 +245,97 @@
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u042b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -302,6 +344,7 @@
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -319,62 +362,77 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -383,6 +441,7 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm b/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
index 11c2ad4..5417bc3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
@@ -126,86 +126,107 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -214,6 +235,7 @@
     label:                              '\u042a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -224,78 +246,97 @@
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u042b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -304,6 +345,7 @@
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -312,6 +354,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -330,62 +373,77 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -394,6 +452,7 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm b/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
index 2eb0f63..5065aa8 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
@@ -118,6 +118,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\\'
 }
 
@@ -125,6 +126,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '|'
 }
 
@@ -132,6 +134,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -139,42 +142,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\''
 }
 
@@ -198,12 +208,14 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0111'
 }
 
@@ -211,6 +223,7 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0110'
 }
 
@@ -218,6 +231,7 @@
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '['
 }
 
@@ -225,6 +239,7 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               ']'
 }
 
@@ -232,18 +247,21 @@
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0142'
 }
 
@@ -251,6 +269,7 @@
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0141'
 }
 
@@ -288,6 +307,7 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '>'
 }
 
@@ -295,6 +315,7 @@
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
     ralt:                               '#'
 }
 
@@ -302,6 +323,7 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '&'
 }
 
@@ -309,6 +331,7 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -316,6 +339,7 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '{'
 }
 
@@ -323,6 +347,7 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '}'
 }
 
@@ -330,6 +355,7 @@
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm b/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
index da9159b..6a63e70 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
@@ -113,18 +113,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -132,42 +135,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -190,60 +200,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d1'
     base:                               '\u00f1'
     shift, capslock:                    '\u00d1'
+    shift+capslock:                     '\u00f1'
 }
 
 key APOSTROPHE {
@@ -257,6 +277,7 @@
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
     ralt:                               '}'
 }
 
@@ -272,42 +293,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm b/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
index 16eb53f..29aab97 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
@@ -109,6 +109,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -116,54 +117,63 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -186,60 +196,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d1'
     base:                               '\u00f1'
     shift, capslock:                    '\u00d1'
+    shift+capslock:                     '\u00f1'
 }
 
 key APOSTROPHE {
@@ -268,42 +288,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm b/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
index 8a4e9a5..f12804f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u00f8'
-    ralt+capslock, shift+ralt:          '\u00d8'
+    shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
     ralt:                               '\u00e6'
-    ralt+capslock, shift+ralt:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key BACKSLASH {
@@ -299,53 +333,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
index 9e20462..6476793 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
@@ -119,18 +119,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -138,42 +141,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -196,54 +206,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -279,42 +298,49 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
index 7fbd1a9..9d6f367 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
@@ -119,18 +119,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -138,42 +141,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -198,54 +208,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -285,42 +304,49 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
index e193d34..2a8fcef 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
@@ -124,6 +124,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -131,12 +132,14 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -144,50 +147,59 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               '\u0131'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          '\u0130'
+    shift+ralt, capslock+ralt:          '\u0130'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u011e'
     base:                               '\u011f'
     shift, capslock:                    '\u011e'
+    shift+capslock:                     '\u011f'
     ralt:                               '\u0308'
 }
 
@@ -195,6 +207,7 @@
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
     ralt:                               '\u0303'
 }
 
@@ -204,14 +217,17 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e6'
-    ralt+shift, ralt+capslock:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u00df'
 }
 
@@ -219,48 +235,56 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u015e'
     base:                               '\u015f'
     shift, capslock:                    '\u015e'
+    shift+capslock:                     '\u015f'
     ralt:                               '\u0301'
 }
 
@@ -268,6 +292,7 @@
     label:                              '\u0130'
     base:                               'i'
     shift, capslock:                    '\u0130'
+    shift+capslock:                     'i'
 }
 
 key COMMA {
@@ -290,54 +315,63 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key EQUALS {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key BACKSLASH {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key PERIOD {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
index 5b96da0..b27f6fa 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
@@ -125,6 +125,7 @@
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '@'
 }
 
@@ -132,32 +133,38 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key E {
     label:                              '\u011f'
     base:                               '\u011f'
     shift, capslock:                    '\u011e'
+    shift+capslock:                     '\u011f'
 }
 
 key R {
     label:                              '\u0131'
     base:                               '\u0131'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00b6'
-    ralt+shift, ralt+capslock:          '\u00ae'
+    shift+ralt, capslock+ralt:          '\u00ae'
+    shift+capslock+ralt:                '\u00b6'
 }
 
 key T {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key Y {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u00a5'
 }
 
@@ -165,26 +172,31 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key I {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key O {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u00f8'
-    ralt+shift, ralt+capslock:          '\u00d8'
+    shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\u00a3'
 }
 
@@ -192,6 +204,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '"'
 }
 
@@ -199,6 +212,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '~'
 }
 
@@ -208,22 +222,27 @@
     label:                              '\u0075'
     base:                               '\u0075'
     shift, capslock:                    '\u0055'
+    shift+capslock:                     '\u0075'
     ralt:                               '\u00e6'
-    ralt+shift, ralt+capslock:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key S {
     label:                              'i'
     base:                               'i'
     shift, capslock:                    '\u0130'
+    shift+capslock:                     'i'
     ralt:                               '\u00df'
-    ralt+shift, ralt+capslock:          '\u00a7'
+    shift+ralt, capslock+ralt:          '\u00a7'
+    shift+capslock+ralt:                '\u00df'
 }
 
 key D {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -231,6 +250,7 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00aa'
 }
 
@@ -238,12 +258,14 @@
     label:                              '\u00fc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key H {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u20ba'
 }
 
@@ -251,24 +273,28 @@
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key K {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
     ralt:                               '\u00b4'
 }
 
@@ -276,6 +302,7 @@
     label:                              '\u015f'
     base:                               '\u015f'
     shift, capslock:                    '\u015e'
+    shift+capslock:                     '\u015f'
 }
 
 key COMMA {
@@ -292,63 +319,76 @@
     base:                               '<'
     shift:                              '>'
     ralt:                               '|'
-    ralt+shift, ralt+capslock:          '\u00a6'
+    shift+ralt, capslock+ralt:          '\u00a6'
+    shift+capslock+ralt:                '|'
 }
 
 key Z {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
     ralt:                               '\u00ab'
-    ralt+shift, ralt+capslock:          '<'
+    shift+ralt, capslock+ralt:          '<'
+    shift+capslock+ralt:                '\u00ab'
 }
 
 key X {
     label:                              '\u00f6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u00bb'
-    ralt+shift, ralt+capslock:          '>'
+    shift+ralt, capslock+ralt:          '>'
+    shift+capslock+ralt:                '\u00bb'
 }
 
 key C {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u00a2'
-    ralt+shift, ralt+capslock:          '\u00a9'
+    shift+ralt, capslock+ralt:          '\u00a9'
+    shift+capslock+ralt:                '\u00a2'
 }
 
 key V {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key B {
     label:                              '\u00e7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key N {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key M {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u00b5'
-    ralt+shift, ralt+capslock:          '\u00ba'
+    shift+ralt, capslock+ralt:          '\u00ba'
+    shift+capslock+ralt:                '\u00b5'
 }
 
 key EQUALS {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u00d7'
 }
 
@@ -356,6 +396,7 @@
     label:                              '.'
     base:                               '.'
     shift, capslock:                    ':'
+    shift+capslock:                     ':'
     ralt:                               '\u00f7'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
index a802460..1346bbb 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
@@ -28,6 +28,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -124,86 +125,107 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -212,6 +234,7 @@
     label:                              '\u0407'
     base:                               '\u0457'
     shift, capslock:                    '\u0407'
+    shift+capslock:                     '\u0457'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -222,78 +245,97 @@
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u0406'
     base:                               '\u0456'
     shift, capslock:                    '\u0406'
+    shift+capslock:                     '\u0456'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -302,6 +344,7 @@
     label:                              '\u0404'
     base:                               '\u0454'
     shift, capslock:                    '\u0404'
+    shift+capslock:                     '\u0454'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -319,6 +362,7 @@
     label:                              '\u0490'
     base:                               '\u0491'
     shift, capslock:                    '\u0490'
+    shift+capslock:                     '\u0491'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -327,62 +371,77 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -391,6 +450,7 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
index bd9e760..c8bcabf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -35,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Class for managing services matching a given intent and requesting a given permission.
@@ -51,12 +52,13 @@
     private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
     private final List<ServiceInfo> mServices = new ArrayList<>();
     private final List<Callback> mCallbacks = new ArrayList<>();
+    private final Predicate mValidator;
 
     private boolean mListening;
 
     private ServiceListing(Context context, String tag,
             String setting, String intentAction, String permission, String noun,
-            boolean addDeviceLockedFlags) {
+            boolean addDeviceLockedFlags, Predicate validator) {
         mContentResolver = context.getContentResolver();
         mContext = context;
         mTag = tag;
@@ -65,6 +67,7 @@
         mPermission = permission;
         mNoun = noun;
         mAddDeviceLockedFlags = addDeviceLockedFlags;
+        mValidator = validator;
     }
 
     public void addCallback(Callback callback) {
@@ -137,7 +140,6 @@
         final PackageManager pmWrapper = mContext.getPackageManager();
         List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
                 new Intent(mIntentAction), flags, user);
-
         for (ResolveInfo resolveInfo : installedServices) {
             ServiceInfo info = resolveInfo.serviceInfo;
 
@@ -148,6 +150,9 @@
                         + mPermission);
                 continue;
             }
+            if (mValidator != null && !mValidator.test(info)) {
+                continue;
+            }
             mServices.add(info);
         }
         for (Callback callback : mCallbacks) {
@@ -194,6 +199,7 @@
         private String mPermission;
         private String mNoun;
         private boolean mAddDeviceLockedFlags = false;
+        private Predicate mValidator;
 
         public Builder(Context context) {
             mContext = context;
@@ -224,6 +230,11 @@
             return this;
         }
 
+        public Builder setValidator(Predicate<ServiceInfo> validator) {
+            mValidator = validator;
+            return this;
+        }
+
         /**
          * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and
          * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results
@@ -236,7 +247,7 @@
 
         public ServiceListing build() {
             return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun,
-                    mAddDeviceLockedFlags);
+                    mAddDeviceLockedFlags, mValidator);
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 688fc72..c4f09ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -31,13 +31,15 @@
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
@@ -116,7 +118,7 @@
     private final boolean mDreamsActivatedOnSleepByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
     private final Set<ComponentName> mDisabledDreams;
-    private final Set<Integer> mSupportedComplications;
+    private Set<Integer> mSupportedComplications;
     private static DreamBackend sInstance;
 
     public static DreamBackend getInstance(Context context) {
@@ -281,7 +283,18 @@
 
     /** Gets all complications which have been enabled by the user. */
     public Set<Integer> getEnabledComplications() {
-        return getComplicationsEnabled() ? mSupportedComplications : Collections.emptySet();
+        final Set<Integer> enabledComplications =
+                getComplicationsEnabled()
+                        ? new ArraySet<>(mSupportedComplications) : new ArraySet<>();
+
+        if (!getHomeControlsEnabled()) {
+            enabledComplications.remove(COMPLICATION_TYPE_HOME_CONTROLS);
+        } else if (mSupportedComplications.contains(COMPLICATION_TYPE_HOME_CONTROLS)) {
+            // Add home control type to list of enabled complications, even if other complications
+            // have been disabled.
+            enabledComplications.add(COMPLICATION_TYPE_HOME_CONTROLS);
+        }
+        return enabledComplications;
     }
 
     /** Sets complication enabled state. */
@@ -290,6 +303,18 @@
                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
     }
 
+    /** Sets whether home controls are enabled by the user on the dream */
+    public void setHomeControlsEnabled(boolean enabled) {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0);
+    }
+
+    /** Gets whether home controls button is enabled on the dream */
+    private boolean getHomeControlsEnabled() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1;
+    }
+
     /**
      * Gets whether complications are enabled on this device
      */
@@ -304,6 +329,14 @@
         return mSupportedComplications;
     }
 
+    /**
+     * Sets the list of supported complications. Should only be used in tests.
+     */
+    @VisibleForTesting
+    public void setSupportedComplications(Set<Integer> complications) {
+        mSupportedComplications = complications;
+    }
+
     public boolean isEnabled() {
         return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 684a9aa..c9e8312 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -31,7 +31,6 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
@@ -621,9 +620,11 @@
             dispatchConnectedDeviceChanged(id);
         }
 
+        /**
+         * Ignore callback here since we'll also receive {@link onRequestFailed} with reason code.
+         */
         @Override
         public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
-            dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
         }
 
         @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 6b9866b..071ab27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -33,7 +33,6 @@
 import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
 import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION_MANAGED;
 import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
 import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
 import static android.media.RouteListingPreference.Item.SUBTEXT_DEVICE_LOW_POWER;
@@ -45,6 +44,7 @@
 import static android.media.RouteListingPreference.Item.SUBTEXT_UNAUTHORIZED;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -95,6 +95,17 @@
         int TYPE_CAST_GROUP_DEVICE = 7;
     }
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SelectionBehavior.SELECTION_BEHAVIOR_NONE,
+            SELECTION_BEHAVIOR_TRANSFER,
+            SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP
+    })
+    public @interface SelectionBehavior {
+        int SELECTION_BEHAVIOR_NONE = 0;
+        int SELECTION_BEHAVIOR_TRANSFER = 1;
+        int SELECTION_BEHAVIOR_GO_TO_APP = 2;
+    }
+
     @VisibleForTesting
     int mType;
 
@@ -213,7 +224,7 @@
      *
      * @return selection behavior of device
      */
-    @RouteListingPreference.Item.SubText
+    @SelectionBehavior
     public int getSelectionBehavior() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
                 ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_TRANSFER;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
index f7fd25b..7ff0988 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -18,20 +18,35 @@
 
 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.anyList;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.provider.Settings;
 
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 public class ServiceListingTest {
 
@@ -39,10 +54,16 @@
     private static final String TEST_INTENT = "com.example.intent";
 
     private ServiceListing mServiceListing;
+    private Context mContext;
+    private PackageManager mPm;
 
     @Before
     public void setUp() {
-        mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application)
+        mPm = mock(PackageManager.class);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getPackageManager()).thenReturn(mPm);
+
+        mServiceListing = new ServiceListing.Builder(mContext)
                 .setTag("testTag")
                 .setSetting(TEST_SETTING)
                 .setNoun("testNoun")
@@ -52,6 +73,81 @@
     }
 
     @Test
+    public void testValidator() {
+        ServiceInfo s1 = new ServiceInfo();
+        s1.permission = "testPermission";
+        s1.packageName = "pkg";
+        ServiceInfo s2 = new ServiceInfo();
+        s2.permission = "testPermission";
+        s2.packageName = "pkg2";
+        ResolveInfo r1 = new ResolveInfo();
+        r1.serviceInfo = s1;
+        ResolveInfo r2 = new ResolveInfo();
+        r2.serviceInfo = s2;
+
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+                ImmutableList.of(r1, r2));
+
+        mServiceListing = new ServiceListing.Builder(mContext)
+                .setTag("testTag")
+                .setSetting(TEST_SETTING)
+                .setNoun("testNoun")
+                .setIntentAction(TEST_INTENT)
+                .setValidator(info -> {
+                    if (info.packageName.equals("pkg")) {
+                        return true;
+                    }
+                    return false;
+                })
+                .setPermission("testPermission")
+                .build();
+        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+        mServiceListing.addCallback(callback);
+        mServiceListing.reload();
+
+        verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+        verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+        assertThat(captor.getValue().size()).isEqualTo(1);
+        assertThat(captor.getValue().get(0)).isEqualTo(s1);
+    }
+
+    @Test
+    public void testNoValidator() {
+        ServiceInfo s1 = new ServiceInfo();
+        s1.permission = "testPermission";
+        s1.packageName = "pkg";
+        ServiceInfo s2 = new ServiceInfo();
+        s2.permission = "testPermission";
+        s2.packageName = "pkg2";
+        ResolveInfo r1 = new ResolveInfo();
+        r1.serviceInfo = s1;
+        ResolveInfo r2 = new ResolveInfo();
+        r2.serviceInfo = s2;
+
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+                ImmutableList.of(r1, r2));
+
+        mServiceListing = new ServiceListing.Builder(mContext)
+                .setTag("testTag")
+                .setSetting(TEST_SETTING)
+                .setNoun("testNoun")
+                .setIntentAction(TEST_INTENT)
+                .setPermission("testPermission")
+                .build();
+        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+        mServiceListing.addCallback(callback);
+        mServiceListing.reload();
+
+        verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+        verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+        assertThat(captor.getValue().size()).isEqualTo(2);
+    }
+
+    @Test
     public void testCallback() {
         ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
         mServiceListing.addCallback(callback);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 52b9227..22ec12d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -16,6 +16,10 @@
 package com.android.settingslib.dream;
 
 
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_DATE;
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_TIME;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -36,13 +40,16 @@
 import org.robolectric.shadows.ShadowSettings;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowSettings.ShadowSecure.class})
 public final class DreamBackendTest {
-    private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3};
+    private static final int[] SUPPORTED_DREAM_COMPLICATIONS =
+            {COMPLICATION_TYPE_HOME_CONTROLS, COMPLICATION_TYPE_DATE,
+                    COMPLICATION_TYPE_TIME};
     private static final List<Integer> SUPPORTED_DREAM_COMPLICATIONS_LIST = Arrays.stream(
             SUPPORTED_DREAM_COMPLICATIONS).boxed().collect(
             Collectors.toList());
@@ -93,8 +100,52 @@
     @Test
     public void testDisableComplications() {
         mBackend.setComplicationsEnabled(false);
-        assertThat(mBackend.getEnabledComplications()).isEmpty();
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactly(COMPLICATION_TYPE_HOME_CONTROLS);
         assertThat(mBackend.getComplicationsEnabled()).isFalse();
     }
-}
 
+    @Test
+    public void testHomeControlsDisabled_ComplicationsEnabled() {
+        mBackend.setComplicationsEnabled(true);
+        mBackend.setHomeControlsEnabled(false);
+        // Home controls should not be enabled, only date and time.
+        final List<Integer> enabledComplications =
+                Arrays.asList(COMPLICATION_TYPE_DATE, COMPLICATION_TYPE_TIME);
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactlyElementsIn(enabledComplications);
+    }
+
+    @Test
+    public void testHomeControlsDisabled_ComplicationsDisabled() {
+        mBackend.setComplicationsEnabled(false);
+        mBackend.setHomeControlsEnabled(false);
+        assertThat(mBackend.getEnabledComplications()).isEmpty();
+    }
+
+    @Test
+    public void testHomeControlsEnabled_ComplicationsDisabled() {
+        mBackend.setComplicationsEnabled(false);
+        mBackend.setHomeControlsEnabled(true);
+        // Home controls should not be enabled, only date and time.
+        final List<Integer> enabledComplications =
+                Collections.singletonList(COMPLICATION_TYPE_HOME_CONTROLS);
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactlyElementsIn(enabledComplications);
+    }
+
+    @Test
+    public void testHomeControlsEnabled_ComplicationsEnabled() {
+        mBackend.setComplicationsEnabled(true);
+        mBackend.setHomeControlsEnabled(true);
+        // Home controls should not be enabled, only date and time.
+        final List<Integer> enabledComplications =
+                Arrays.asList(
+                        COMPLICATION_TYPE_HOME_CONTROLS,
+                        COMPLICATION_TYPE_DATE,
+                        COMPLICATION_TYPE_TIME
+                );
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactlyElementsIn(enabledComplications);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index f63c06a..270fda8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -799,12 +800,12 @@
     }
 
     @Test
-    public void onTransferFailed_shouldDispatchOnRequestFailed() {
+    public void onTransferFailed_notDispatchOnRequestFailed() {
         mInfoMediaManager.registerCallback(mCallback);
 
         mInfoMediaManager.mMediaRouterCallback.onTransferFailed(null, null);
 
-        verify(mCallback).onRequestFailed(REASON_UNKNOWN_ERROR);
+        verify(mCallback, never()).onRequestFailed(REASON_UNKNOWN_ERROR);
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 09a1ba2..e50f522 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -49,6 +49,7 @@
         Settings.Global.CHARGING_SOUNDS_ENABLED,
         Settings.Global.USB_MASS_STORAGE_ENABLED,
         Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+        Settings.Global.NETWORK_AVOID_BAD_WIFI,
         Settings.Global.WIFI_WAKEUP_ENABLED,
         Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
         Settings.Global.USE_OPEN_WIFI_PACKAGE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f66fcba..3efb41d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -139,6 +139,7 @@
         Settings.Secure.SCREENSAVER_COMPONENTS,
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+        Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
         Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
         Settings.Secure.VOLUME_HUSH_GESTURE,
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index e57cf3b..8d07fb6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,9 @@
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
 import static android.media.AudioFormat.SURROUND_SOUND_ENCODING;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_AVOID;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
@@ -103,6 +106,14 @@
         VALIDATORS.put(
                 Global.NETWORK_RECOMMENDATIONS_ENABLED,
                 new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
+        VALIDATORS.put(
+                Global.NETWORK_AVOID_BAD_WIFI,
+                new DiscreteValueValidator(
+                        new String[] {
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_IGNORE),
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_PROMPT),
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_AVOID),
+                        }));
         VALIDATORS.put(Global.WIFI_WAKEUP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 558e19f..abd2c75 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -206,6 +206,7 @@
         VALIDATORS.put(Secure.SCREENSAVER_COMPONENTS, COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_DOCK, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index db6cc1a..a4c59ea 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.MemoryIntArray;
@@ -81,6 +82,10 @@
     }
 
     private void incrementGenerationInternal(int key, @NonNull String indexMapKey) {
+        if (SettingsState.isGlobalSettingsKey(key)) {
+            // Global settings are shared across users, so ignore the userId in the key
+            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+        }
         synchronized (mLock) {
             final MemoryIntArray backingStore = getBackingStoreLocked(key,
                     /* createIfNotExist= */ false);
@@ -126,6 +131,10 @@
      *  returning the result.
      */
     public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
+        if (SettingsState.isGlobalSettingsKey(key)) {
+            // Global settings are shared across users, so ignore the userId in the key
+            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+        }
         synchronized (mLock) {
             final MemoryIntArray backingStore = getBackingStoreLocked(key,
                     /* createIfNotExist= */ true);
@@ -140,11 +149,9 @@
                     // Should not happen unless having error accessing the backing store
                     return;
                 }
-                bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                        backingStore);
+                bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, backingStore);
                 bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
-                bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                        backingStore.get(index));
+                bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY, backingStore.get(index));
                 if (DEBUG) {
                     Slog.i(LOG_TAG, "Exported index:" + index + " for "
                             + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e6408bf..7607909 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,6 +35,13 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
+import static com.android.providers.settings.SettingsState.getTypeFromKey;
+import static com.android.providers.settings.SettingsState.getUserIdFromKey;
+import static com.android.providers.settings.SettingsState.isConfigSettingsKey;
+import static com.android.providers.settings.SettingsState.isGlobalSettingsKey;
+import static com.android.providers.settings.SettingsState.isSecureSettingsKey;
+import static com.android.providers.settings.SettingsState.isSsaidSettingsKey;
+import static com.android.providers.settings.SettingsState.isSystemSettingsKey;
 import static com.android.providers.settings.SettingsState.makeKey;
 
 import android.Manifest;
@@ -376,14 +383,6 @@
     @GuardedBy("mLock")
     private boolean mSyncConfigDisabledUntilReboot;
 
-    public static int getTypeFromKey(int key) {
-        return SettingsState.getTypeFromKey(key);
-    }
-
-    public static int getUserIdFromKey(int key) {
-        return SettingsState.getUserIdFromKey(key);
-    }
-
     @ChangeId
     @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
     private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -3620,26 +3619,6 @@
             }
         }
 
-        private boolean isConfigSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
-        }
-
-        private boolean isGlobalSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
-        }
-
-        private boolean isSystemSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
-        }
-
-        private boolean isSecureSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
-        }
-
-        private boolean isSsaidSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
-        }
-
         private boolean shouldBan(int type) {
             if (SETTINGS_TYPE_CONFIG != type) {
                 return false;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 4d8705f..e3153e0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -255,6 +255,26 @@
         }
     }
 
+    public static boolean isConfigSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
+    }
+
+    public static boolean isGlobalSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
+    }
+
+    public static boolean isSystemSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
+    }
+
+    public static boolean isSecureSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
+    }
+
+    public static boolean isSsaidSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
+    }
+
     public static String keyToString(int key) {
         return "Key[user=" + getUserIdFromKey(key) + ";type="
                 + settingTypeToString(getTypeFromKey(key)) + "]";
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 278ceb9..1f14723 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -372,7 +372,6 @@
                     Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS,
                     Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH,
                     Settings.Global.NETPOLICY_OVERRIDE_ENABLED,
-                    Settings.Global.NETWORK_AVOID_BAD_WIFI,
                     Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
                     Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
                     Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
new file mode 100644
index 0000000..78ae4af
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
@@ -0,0 +1,59 @@
+package com.android.systemui.animation
+
+private const val TAG_WGHT = "wght"
+private const val TAG_WDTH = "wdth"
+private const val TAG_OPSZ = "opsz"
+private const val TAG_ROND = "ROND"
+
+class FontVariationUtils {
+    private var mWeight = -1
+    private var mWidth = -1
+    private var mOpticalSize = -1
+    private var mRoundness = -1
+    private var isUpdated = false
+
+    /*
+     * generate fontVariationSettings string, used for key in typefaceCache in TextAnimator
+     * the order of axes should align to the order of parameters
+     * if every axis remains unchanged, return ""
+     */
+    fun updateFontVariation(
+        weight: Int = -1,
+        width: Int = -1,
+        opticalSize: Int = -1,
+        roundness: Int = -1
+    ): String {
+        isUpdated = false
+        if (weight >= 0 && mWeight != weight) {
+            isUpdated = true
+            mWeight = weight
+        }
+        if (width >= 0 && mWidth != width) {
+            isUpdated = true
+            mWidth = width
+        }
+        if (opticalSize >= 0 && mOpticalSize != opticalSize) {
+            isUpdated = true
+            mOpticalSize = opticalSize
+        }
+
+        if (roundness >= 0 && mRoundness != roundness) {
+            isUpdated = true
+            mRoundness = roundness
+        }
+        var resultString = ""
+        if (mWeight >= 0) {
+            resultString += "'$TAG_WGHT' $mWeight"
+        }
+        if (mWidth >= 0) {
+            resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth"
+        }
+        if (mOpticalSize >= 0) {
+            resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize"
+        }
+        if (mRoundness >= 0) {
+            resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness"
+        }
+        return if (isUpdated) resultString else ""
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 6946e6b..03e1e66 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -59,12 +59,14 @@
             // changes should be ordered top-to-bottom in z
             val mode = change.mode
 
+            var rootIdx = info.findRootIndex(change.endDisplayId)
+            if (rootIdx < 0) rootIdx = 0
             // Launcher animates leaf tasks directly, so always reparent all task leashes to root.
-            t.reparent(leash, info.rootLeash)
+            t.reparent(leash, info.getRoot(rootIdx).leash)
             t.setPosition(
                 leash,
-                (change.startAbsBounds.left - info.rootOffset.x).toFloat(),
-                (change.startAbsBounds.top - info.rootOffset.y).toFloat()
+                (change.startAbsBounds.left - info.getRoot(rootIdx).offset.x).toFloat(),
+                (change.startAbsBounds.top - info.getRoot(rootIdx).offset.y).toFloat()
             )
             t.show(leash)
             // Put all the OPEN/SHOW on top
@@ -114,8 +116,11 @@
                     .setName(change.leash.toString() + "_transition-leash")
                     .setContainerLayer()
                     .setParent(
-                        if (change.parent == null) info.rootLeash
-                        else info.getChange(change.parent!!)!!.leash
+                        if (change.parent == null) {
+                            var rootIdx = info.findRootIndex(change.endDisplayId)
+                            if (rootIdx < 0) rootIdx = 0
+                            info.getRoot(rootIdx).leash
+                        } else info.getChange(change.parent!!)!!.leash
                     )
                     .build()
             // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 7fe94d3..9e9929e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -23,11 +23,8 @@
 import android.graphics.Canvas
 import android.graphics.Typeface
 import android.graphics.fonts.Font
-import android.graphics.fonts.FontVariationAxis
 import android.text.Layout
-import android.util.SparseArray
 
-private const val TAG_WGHT = "wght"
 private const val DEFAULT_ANIMATION_DURATION: Long = 300
 
 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
@@ -51,7 +48,7 @@
  *
  *         // Change the text size with animation.
  *         fun setTextSize(sizePx: Float, animate: Boolean) {
- *             animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
+ *             animator.setTextStyle("" /* unchanged fvar... */, sizePx, animate)
  *         }
  *     }
  * ```
@@ -115,7 +112,9 @@
             protected set
     }
 
-    private val typefaceCache = SparseArray<Typeface?>()
+    private val fontVariationUtils = FontVariationUtils()
+
+    private val typefaceCache = HashMap<String, Typeface?>()
 
     fun updateLayout(layout: Layout) {
         textInterpolator.layout = layout
@@ -186,7 +185,7 @@
      * Bu passing -1 to duration, the default text animation, 1000ms, is used.
      * By passing false to animate, the text will be updated without animation.
      *
-     * @param weight an optional text weight.
+     * @param fvar an optional text fontVariationSettings.
      * @param textSize an optional font size.
      * @param colors an optional colors array that must be the same size as numLines passed to
      *               the TextInterpolator
@@ -199,7 +198,7 @@
      *                     will be used. This is ignored if animate is false.
      */
     fun setTextStyle(
-        weight: Int = -1,
+        fvar: String? = "",
         textSize: Float = -1f,
         color: Int? = null,
         strokeWidth: Float = -1f,
@@ -217,42 +216,16 @@
         if (textSize >= 0) {
             textInterpolator.targetPaint.textSize = textSize
         }
-        if (weight >= 0) {
-            val fontVariationArray =
-                    FontVariationAxis.fromFontVariationSettings(
-                        textInterpolator.targetPaint.fontVariationSettings
-                    )
-            if (fontVariationArray.isNullOrEmpty()) {
-                textInterpolator.targetPaint.typeface =
-                    typefaceCache.getOrElse(weight) {
-                        textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
-                        textInterpolator.targetPaint.typeface
-                    }
-            } else {
-                val idx = fontVariationArray.indexOfFirst { it.tag == "$TAG_WGHT" }
-                if (idx == -1) {
-                    val updatedFontVariation =
-                        textInterpolator.targetPaint.fontVariationSettings + ",'$TAG_WGHT' $weight"
-                    textInterpolator.targetPaint.typeface =
-                        typefaceCache.getOrElse(weight) {
-                            textInterpolator.targetPaint.fontVariationSettings =
-                                    updatedFontVariation
-                            textInterpolator.targetPaint.typeface
-                        }
-                } else {
-                    fontVariationArray[idx] = FontVariationAxis(
-                            "$TAG_WGHT", weight.toFloat())
-                    val updatedFontVariation =
-                            FontVariationAxis.toFontVariationSettings(fontVariationArray)
-                    textInterpolator.targetPaint.typeface =
-                        typefaceCache.getOrElse(weight) {
-                            textInterpolator.targetPaint.fontVariationSettings =
-                                    updatedFontVariation
-                            textInterpolator.targetPaint.typeface
-                        }
+
+        if (!fvar.isNullOrBlank()) {
+            textInterpolator.targetPaint.typeface =
+                typefaceCache.getOrElse(fvar) {
+                    textInterpolator.targetPaint.fontVariationSettings = fvar
+                    typefaceCache.put(fvar, textInterpolator.targetPaint.typeface)
+                    textInterpolator.targetPaint.typeface
                 }
-            }
         }
+
         if (color != null) {
             textInterpolator.targetPaint.color = color
         }
@@ -291,13 +264,56 @@
             invalidateCallback()
         }
     }
+
+    /**
+     * Set text style with animation. Similar as
+     * fun setTextStyle(
+     *      fvar: String? = "",
+     *      textSize: Float = -1f,
+     *      color: Int? = null,
+     *      strokeWidth: Float = -1f,
+     *      animate: Boolean = true,
+     *      duration: Long = -1L,
+     *      interpolator: TimeInterpolator? = null,
+     *      delay: Long = 0,
+     *      onAnimationEnd: Runnable? = null
+     * )
+     *
+     * @param weight an optional style value for `wght` in fontVariationSettings.
+     * @param width an optional style value for `wdth` in fontVariationSettings.
+     * @param opticalSize an optional style value for `opsz` in fontVariationSettings.
+     * @param roundness an optional style value for `ROND` in fontVariationSettings.
+     */
+    fun setTextStyle(
+        weight: Int = -1,
+        width: Int = -1,
+        opticalSize: Int = -1,
+        roundness: Int = -1,
+        textSize: Float = -1f,
+        color: Int? = null,
+        strokeWidth: Float = -1f,
+        animate: Boolean = true,
+        duration: Long = -1L,
+        interpolator: TimeInterpolator? = null,
+        delay: Long = 0,
+        onAnimationEnd: Runnable? = null
+    ) {
+        val fvar = fontVariationUtils.updateFontVariation(
+            weight = weight,
+            width = width,
+            opticalSize = opticalSize,
+            roundness = roundness,)
+        setTextStyle(
+            fvar = fvar,
+            textSize = textSize,
+            color = color,
+            strokeWidth = strokeWidth,
+            animate = animate,
+            duration = duration,
+            interpolator = interpolator,
+            delay = delay,
+            onAnimationEnd = onAnimationEnd,
+        )
+    }
 }
 
-private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V {
-    var v = get(key)
-    if (v == null) {
-        v = defaultValue()
-        put(key, v)
-    }
-    return v
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index d78e0c1..280e7ed9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -53,6 +53,19 @@
                                 cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
             }
 
+            // Perceived luminosity (L′), not absolute luminosity.
+            half getLuminosity(vec3 c) {
+                return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
+            }
+
+            // Creates a luminosity mask and clamp to the legal range.
+            vec3 maskLuminosity(vec3 dest, float lum) {
+                dest.rgb *= vec3(lum);
+                // Clip back into the legal range
+                dest = clamp(dest, vec3(0.), vec3(1.0));
+                return dest;
+            }
+
             // Return range [-1, 1].
             vec3 hash(vec3 p) {
                 p = fract(p * vec3(.3456, .1234, .9876));
@@ -62,14 +75,14 @@
             }
 
             // Skew factors (non-uniform).
-            const float SKEW = 0.3333333;  // 1/3
-            const float UNSKEW = 0.1666667;  // 1/6
+            const half SKEW = 0.3333333;  // 1/3
+            const half UNSKEW = 0.1666667;  // 1/6
 
             // Return range roughly [-1,1].
             // It's because the hash function (that returns a random gradient vector) returns
             // different magnitude of vectors. Noise doesn't have to be in the precise range thus
             // skipped normalize.
-            float simplex3d(vec3 p) {
+            half simplex3d(vec3 p) {
                 // Skew the input coordinate, so that we get squashed cubical grid
                 vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
 
@@ -143,6 +156,22 @@
                 // Should multiply by the possible max contribution to adjust the range in [-1,1].
                 return dot(vec4(32.), nc);
             }
+
+            // Random rotations.
+            // The way you create fractal noise is layering simplex noise with some rotation.
+            // To make random cloud looking noise, the rotations should not align. (Otherwise it
+            // creates patterned noise).
+            // Below rotations only rotate in one axis.
+            const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15);
+            const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95);
+            const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44);
+
+            // Octave = 4
+            // Divide each coefficient by 3 to produce more grainy noise.
+            half simplex3d_fractal(vec3 mat) {
+                return 0.675 * simplex3d(mat * rot1) + 0.225 * simplex3d(2.0 * mat * rot2)
+                        + 0.075 * simplex3d(4.0 * mat * rot3) + 0.025 * simplex3d(8.0 * mat);
+            }
             """
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 7456c43..0e22667 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -19,8 +19,13 @@
 import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
 import java.lang.Float.max
 
-/** Shader that renders turbulence simplex noise, with no octave. */
-class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+/**
+ * Shader that renders turbulence simplex noise, by default no octave.
+ *
+ * @param useFractal whether to use fractal noise (4 octaves).
+ */
+class TurbulenceNoiseShader(useFractal: Boolean = false) :
+    RuntimeShader(if (useFractal) FRACTAL_NOISE_SHADER else SIMPLEX_NOISE_SHADER) {
     // language=AGSL
     companion object {
         private const val UNIFORMS =
@@ -35,21 +40,7 @@
             layout(color) uniform vec4 in_backgroundColor;
         """
 
-        private const val SHADER_LIB =
-            """
-            float getLuminosity(vec3 c) {
-                return 0.3*c.r + 0.59*c.g + 0.11*c.b;
-            }
-
-            vec3 maskLuminosity(vec3 dest, float lum) {
-                dest.rgb *= vec3(lum);
-                // Clip back into the legal range
-                dest = clamp(dest, vec3(0.), vec3(1.0));
-                return dest;
-            }
-        """
-
-        private const val MAIN_SHADER =
+        private const val SIMPLEX_SHADER =
             """
             vec4 main(vec2 p) {
                 vec2 uv = p / in_size.xy;
@@ -71,8 +62,26 @@
             }
         """
 
-        private const val TURBULENCE_NOISE_SHADER =
-            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+        private const val FRACTAL_SHADER =
+            """
+            vec4 main(vec2 p) {
+                vec2 uv = p / in_size.xy;
+                uv.x *= in_aspectRatio;
+
+                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+                float luma = simplex3d_fractal(noiseP) * in_opacity;
+                vec3 mask = maskLuminosity(in_color.rgb, luma);
+                vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+                // Skip dithering.
+                return vec4(color * in_color.a, in_color.a);
+            }
+        """
+
+        private const val SIMPLEX_NOISE_SHADER =
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER
+        private const val FRACTAL_NOISE_SHADER =
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER
     }
 
     /** Sets the number of grid for generating noise. */
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
index 459a38e..09762b0 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -25,10 +25,12 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.EnumSet
 import java.util.regex.Pattern
 import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UElement
 
+@Suppress("UnstableApiUsage") // For linter api
 class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner {
     override fun getApplicableUastTypes(): List<Class<out UElement>> {
         return listOf(UAnnotation::class.java)
@@ -39,18 +41,15 @@
             override fun visitAnnotation(node: UAnnotation) {
                 // Annotations having int bugId field
                 if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) {
-                    val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
-                    if (bugId <= 0) {
+                    if (!containsBugId(node)) {
                         val location = context.getLocation(node)
                         val message = "Please attach a bug id to track demoted test"
                         context.report(ISSUE, node, location, message)
                     }
                 }
-                // @Ignore has a String field for reason
+                // @Ignore has a String field for specifying reasons
                 if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) {
-                    val reason = node.findAttributeValue("value")!!.evaluate() as String
-                    val bugPattern = Pattern.compile("b/\\d+")
-                    if (!bugPattern.matcher(reason).find()) {
+                    if (!containsBugString(node)) {
                         val location = context.getLocation(node)
                         val message = "Please attach a bug (e.g. b/123) to track demoted test"
                         context.report(ISSUE, node, location, message)
@@ -60,6 +59,17 @@
         }
     }
 
+    private fun containsBugId(node: UAnnotation): Boolean {
+        val bugId = node.findAttributeValue("bugId")?.evaluate() as Int?
+        return bugId != null && bugId > 0
+    }
+
+    private fun containsBugString(node: UAnnotation): Boolean {
+        val reason = node.findAttributeValue("value")?.evaluate() as String?
+        val bugPattern = Pattern.compile("b/\\d+")
+        return reason != null && bugPattern.matcher(reason).find()
+    }
+
     companion object {
         val DEMOTING_ANNOTATION_BUG_ID =
             listOf(
@@ -87,7 +97,7 @@
                 implementation =
                     Implementation(
                         DemotingTestWithoutBugDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
+                        EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
                     )
             )
     }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index 63eb263..a1e6f92 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -20,8 +20,11 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.Scope
+import java.util.EnumSet
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
@@ -45,6 +48,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -65,6 +69,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -88,6 +93,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -115,6 +121,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -145,6 +152,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -168,6 +176,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -198,6 +207,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -221,6 +231,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -248,6 +259,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -260,6 +272,7 @@
             )
     }
 
+    private val testScope = EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
     private val filtersFlakyTestStub: TestFile =
         java(
             """
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
new file mode 100644
index 0000000..946e779
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
@@ -0,0 +1,849 @@
+/*
+ * 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.swipeable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.DraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.compose.swipeable.SwipeableDefaults.AnimationSpec
+import com.android.compose.swipeable.SwipeableDefaults.StandardResistanceFactor
+import com.android.compose.swipeable.SwipeableDefaults.VelocityThreshold
+import com.android.compose.swipeable.SwipeableDefaults.resistanceConfig
+import com.android.compose.ui.util.lerp
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlin.math.sin
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * State of the [swipeable] modifier.
+ *
+ * This contains necessary information about any ongoing swipe or animation and provides methods to
+ * change the state either immediately or by starting an animation. To create and remember a
+ * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ *
+ * TODO(b/272311106): this is a fork from material. Unfork it when Swipeable.kt reaches material3.
+ */
+@Stable
+open class SwipeableState<T>(
+    initialValue: T,
+    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
+    internal val confirmStateChange: (newValue: T) -> Boolean = { true }
+) {
+    /**
+     * The current value of the state.
+     *
+     * If no swipe or animation is in progress, this corresponds to the anchor at which the
+     * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
+     * the last anchor at which the [swipeable] was settled before the swipe or animation started.
+     */
+    var currentValue: T by mutableStateOf(initialValue)
+        private set
+
+    /** Whether the state is currently animating. */
+    var isAnimationRunning: Boolean by mutableStateOf(false)
+        private set
+
+    /**
+     * The current position (in pixels) of the [swipeable].
+     *
+     * You should use this state to offset your content accordingly. The recommended way is to use
+     * `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
+     */
+    val offset: State<Float>
+        get() = offsetState
+
+    /** The amount by which the [swipeable] has been swiped past its bounds. */
+    val overflow: State<Float>
+        get() = overflowState
+
+    // Use `Float.NaN` as a placeholder while the state is uninitialised.
+    private val offsetState = mutableStateOf(0f)
+    private val overflowState = mutableStateOf(0f)
+
+    // the source of truth for the "real"(non ui) position
+    // basically position in bounds + overflow
+    private val absoluteOffset = mutableStateOf(0f)
+
+    // current animation target, if animating, otherwise null
+    private val animationTarget = mutableStateOf<Float?>(null)
+
+    internal var anchors by mutableStateOf(emptyMap<Float, T>())
+
+    private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
+        snapshotFlow { anchors }.filter { it.isNotEmpty() }.take(1)
+
+    internal var minBound = Float.NEGATIVE_INFINITY
+    internal var maxBound = Float.POSITIVE_INFINITY
+
+    internal fun ensureInit(newAnchors: Map<Float, T>) {
+        if (anchors.isEmpty()) {
+            // need to do initial synchronization synchronously :(
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+            offsetState.value = initialOffset
+            absoluteOffset.value = initialOffset
+        }
+    }
+
+    internal suspend fun processNewAnchors(oldAnchors: Map<Float, T>, newAnchors: Map<Float, T>) {
+        if (oldAnchors.isEmpty()) {
+            // If this is the first time that we receive anchors, then we need to initialise
+            // the state so we snap to the offset associated to the initial value.
+            minBound = newAnchors.keys.minOrNull()!!
+            maxBound = newAnchors.keys.maxOrNull()!!
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+            snapInternalToOffset(initialOffset)
+        } else if (newAnchors != oldAnchors) {
+            // If we have received new anchors, then the offset of the current value might
+            // have changed, so we need to animate to the new offset. If the current value
+            // has been removed from the anchors then we animate to the closest anchor
+            // instead. Note that this stops any ongoing animation.
+            minBound = Float.NEGATIVE_INFINITY
+            maxBound = Float.POSITIVE_INFINITY
+            val animationTargetValue = animationTarget.value
+            // if we're in the animation already, let's find it a new home
+            val targetOffset =
+                if (animationTargetValue != null) {
+                    // first, try to map old state to the new state
+                    val oldState = oldAnchors[animationTargetValue]
+                    val newState = newAnchors.getOffset(oldState)
+                    // return new state if exists, or find the closes one among new anchors
+                    newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
+                } else {
+                    // we're not animating, proceed by finding the new anchors for an old value
+                    val actualOldValue = oldAnchors[offset.value]
+                    val value = if (actualOldValue == currentValue) currentValue else actualOldValue
+                    newAnchors.getOffset(value)
+                        ?: newAnchors.keys.minByOrNull { abs(it - offset.value) }!!
+                }
+            try {
+                animateInternalToOffset(targetOffset, animationSpec)
+            } catch (c: CancellationException) {
+                // If the animation was interrupted for any reason, snap as a last resort.
+                snapInternalToOffset(targetOffset)
+            } finally {
+                currentValue = newAnchors.getValue(targetOffset)
+                minBound = newAnchors.keys.minOrNull()!!
+                maxBound = newAnchors.keys.maxOrNull()!!
+            }
+        }
+    }
+
+    internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
+
+    internal var velocityThreshold by mutableStateOf(0f)
+
+    internal var resistance: ResistanceConfig? by mutableStateOf(null)
+
+    internal val draggableState = DraggableState {
+        val newAbsolute = absoluteOffset.value + it
+        val clamped = newAbsolute.coerceIn(minBound, maxBound)
+        val overflow = newAbsolute - clamped
+        val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
+        offsetState.value = clamped + resistanceDelta
+        overflowState.value = overflow
+        absoluteOffset.value = newAbsolute
+    }
+
+    private suspend fun snapInternalToOffset(target: Float) {
+        draggableState.drag { dragBy(target - absoluteOffset.value) }
+    }
+
+    private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
+        draggableState.drag {
+            var prevValue = absoluteOffset.value
+            animationTarget.value = target
+            isAnimationRunning = true
+            try {
+                Animatable(prevValue).animateTo(target, spec) {
+                    dragBy(this.value - prevValue)
+                    prevValue = this.value
+                }
+            } finally {
+                animationTarget.value = null
+                isAnimationRunning = false
+            }
+        }
+    }
+
+    /**
+     * The target value of the state.
+     *
+     * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
+     * swipe finished. If an animation is running, this is the target value of that animation.
+     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+     */
+    val targetValue: T
+        get() {
+            // TODO(calintat): Track current velocity (b/149549482) and use that here.
+            val target =
+                animationTarget.value
+                    ?: computeTarget(
+                        offset = offset.value,
+                        lastValue = anchors.getOffset(currentValue) ?: offset.value,
+                        anchors = anchors.keys,
+                        thresholds = thresholds,
+                        velocity = 0f,
+                        velocityThreshold = Float.POSITIVE_INFINITY
+                    )
+            return anchors[target] ?: currentValue
+        }
+
+    /**
+     * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
+     *
+     * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
+     */
+    val progress: SwipeProgress<T>
+        get() {
+            val bounds = findBounds(offset.value, anchors.keys)
+            val from: T
+            val to: T
+            val fraction: Float
+            when (bounds.size) {
+                0 -> {
+                    from = currentValue
+                    to = currentValue
+                    fraction = 1f
+                }
+                1 -> {
+                    from = anchors.getValue(bounds[0])
+                    to = anchors.getValue(bounds[0])
+                    fraction = 1f
+                }
+                else -> {
+                    val (a, b) =
+                        if (direction > 0f) {
+                            bounds[0] to bounds[1]
+                        } else {
+                            bounds[1] to bounds[0]
+                        }
+                    from = anchors.getValue(a)
+                    to = anchors.getValue(b)
+                    fraction = (offset.value - a) / (b - a)
+                }
+            }
+            return SwipeProgress(from, to, fraction)
+        }
+
+    /**
+     * The direction in which the [swipeable] is moving, relative to the current [currentValue].
+     *
+     * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
+     * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
+     */
+    val direction: Float
+        get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
+
+    /**
+     * Set the state without any animation and suspend until it's set
+     *
+     * @param targetValue The new target value to set [currentValue] to.
+     */
+    suspend fun snapTo(targetValue: T) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val targetOffset = anchors.getOffset(targetValue)
+            requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+            snapInternalToOffset(targetOffset)
+            currentValue = targetValue
+        }
+    }
+
+    /**
+     * Set the state to the target value by starting an animation.
+     *
+     * @param targetValue The new value to animate to.
+     * @param anim The animation that will be used to animate to the new value.
+     */
+    suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            try {
+                val targetOffset = anchors.getOffset(targetValue)
+                requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+                animateInternalToOffset(targetOffset, anim)
+            } finally {
+                val endOffset = absoluteOffset.value
+                val endValue =
+                    anchors
+                        // fighting rounding error once again, anchor should be as close as 0.5
+                        // pixels
+                        .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
+                        .values
+                        .firstOrNull()
+                        ?: currentValue
+                currentValue = endValue
+            }
+        }
+    }
+
+    /**
+     * Perform fling with settling to one of the anchors which is determined by the given
+     * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
+     * since it will settle at the anchor.
+     *
+     * In general cases, [swipeable] flings by itself when being swiped. This method is to be used
+     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+     * trigger settling fling when the child scroll container reaches the bound.
+     *
+     * @param velocity velocity to fling and settle with
+     * @return the reason fling ended
+     */
+    suspend fun performFling(velocity: Float) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val lastAnchor = anchors.getOffset(currentValue)!!
+            val targetValue =
+                computeTarget(
+                    offset = offset.value,
+                    lastValue = lastAnchor,
+                    anchors = anchors.keys,
+                    thresholds = thresholds,
+                    velocity = velocity,
+                    velocityThreshold = velocityThreshold
+                )
+            val targetState = anchors[targetValue]
+            if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
+            // If the user vetoed the state change, rollback to the previous state.
+            else animateInternalToOffset(lastAnchor, animationSpec)
+        }
+    }
+
+    /**
+     * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
+     * gesture flow.
+     *
+     * Note: This method performs generic drag and it won't settle to any particular anchor, *
+     * leaving swipeable in between anchors. When done dragging, [performFling] must be called as
+     * well to ensure swipeable will settle at the anchor.
+     *
+     * In general cases, [swipeable] drags by itself when being swiped. This method is to be used
+     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+     * force drag when the child scroll container reaches the bound.
+     *
+     * @param delta delta in pixels to drag by
+     * @return the amount of [delta] consumed
+     */
+    fun performDrag(delta: Float): Float {
+        val potentiallyConsumed = absoluteOffset.value + delta
+        val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
+        val deltaToConsume = clamped - absoluteOffset.value
+        if (abs(deltaToConsume) > 0) {
+            draggableState.dispatchRawDelta(deltaToConsume)
+        }
+        return deltaToConsume
+    }
+
+    companion object {
+        /** The default [Saver] implementation for [SwipeableState]. */
+        fun <T : Any> Saver(
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (T) -> Boolean
+        ) =
+            Saver<SwipeableState<T>, T>(
+                save = { it.currentValue },
+                restore = { SwipeableState(it, animationSpec, confirmStateChange) }
+            )
+    }
+}
+
+/**
+ * Collects information about the ongoing swipe or animation in [swipeable].
+ *
+ * To access this information, use [SwipeableState.progress].
+ *
+ * @param from The state corresponding to the anchor we are moving away from.
+ * @param to The state corresponding to the anchor we are moving towards.
+ * @param fraction The fraction that the current position represents between [from] and [to]. Must
+ *   be between `0` and `1`.
+ */
+@Immutable
+class SwipeProgress<T>(
+    val from: T,
+    val to: T,
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    val fraction: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is SwipeProgress<*>) return false
+
+        if (from != other.from) return false
+        if (to != other.to) return false
+        if (fraction != other.fraction) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = from?.hashCode() ?: 0
+        result = 31 * result + (to?.hashCode() ?: 0)
+        result = 31 * result + fraction.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun <T : Any> rememberSwipeableState(
+    initialValue: T,
+    animationSpec: AnimationSpec<Float> = AnimationSpec,
+    confirmStateChange: (newValue: T) -> Boolean = { true }
+): SwipeableState<T> {
+    return rememberSaveable(
+        saver =
+            SwipeableState.Saver(
+                animationSpec = animationSpec,
+                confirmStateChange = confirmStateChange
+            )
+    ) {
+        SwipeableState(
+            initialValue = initialValue,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
+ * 1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
+ * 2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
+ *    [value] will be notified to update their state to the new value of the [SwipeableState] by
+ *    invoking [onValueChange]. If the owner does not update their state to the provided value for
+ *    some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
+ */
+@Composable
+internal fun <T : Any> rememberSwipeableStateFor(
+    value: T,
+    onValueChange: (T) -> Unit,
+    animationSpec: AnimationSpec<Float> = AnimationSpec
+): SwipeableState<T> {
+    val swipeableState = remember {
+        SwipeableState(
+            initialValue = value,
+            animationSpec = animationSpec,
+            confirmStateChange = { true }
+        )
+    }
+    val forceAnimationCheck = remember { mutableStateOf(false) }
+    LaunchedEffect(value, forceAnimationCheck.value) {
+        if (value != swipeableState.currentValue) {
+            swipeableState.animateTo(value)
+        }
+    }
+    DisposableEffect(swipeableState.currentValue) {
+        if (value != swipeableState.currentValue) {
+            onValueChange(swipeableState.currentValue)
+            forceAnimationCheck.value = !forceAnimationCheck.value
+        }
+        onDispose {}
+    }
+    return swipeableState
+}
+
+/**
+ * Enable swipe gestures between a set of predefined states.
+ *
+ * To use this, you must provide a map of anchors (in pixels) to states (of type [T]). Note that
+ * this map cannot be empty and cannot have two anchors mapped to the same state.
+ *
+ * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
+ * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
+ * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [SwipeableState] will also be updated to the state corresponding to the
+ * new anchor. The target anchor is calculated based on the provided positional [thresholds].
+ *
+ * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
+ * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
+ *
+ * For an example of a [swipeable] with three states, see:
+ *
+ * @param T The type of the state.
+ * @param state The state of the [swipeable].
+ * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
+ * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
+ *   used to determine which state to animate to when swiping stops. This is represented as a lambda
+ *   that takes two states and returns the threshold between them in the form of a
+ *   [ThresholdConfig]. Note that the order of the states corresponds to the swipe direction.
+ * @param orientation The orientation in which the [swipeable] can be swiped.
+ * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom swipe
+ *   will behave like bottom to top, and a left to right swipe will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
+ *   [Modifier.draggable].
+ * @param resistance Controls how much resistance will be applied when swiping past the bounds.
+ * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed in
+ *   order to animate to the next state, even if the positional [thresholds] have not been reached.
+ * @sample androidx.compose.material.samples.SwipeableSample
+ */
+fun <T> Modifier.swipeable(
+    state: SwipeableState<T>,
+    anchors: Map<Float, T>,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    reverseDirection: Boolean = false,
+    interactionSource: MutableInteractionSource? = null,
+    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
+    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+    velocityThreshold: Dp = VelocityThreshold
+) =
+    composed(
+        inspectorInfo =
+            debugInspectorInfo {
+                name = "swipeable"
+                properties["state"] = state
+                properties["anchors"] = anchors
+                properties["orientation"] = orientation
+                properties["enabled"] = enabled
+                properties["reverseDirection"] = reverseDirection
+                properties["interactionSource"] = interactionSource
+                properties["thresholds"] = thresholds
+                properties["resistance"] = resistance
+                properties["velocityThreshold"] = velocityThreshold
+            }
+    ) {
+        require(anchors.isNotEmpty()) { "You must have at least one anchor." }
+        require(anchors.values.distinct().count() == anchors.size) {
+            "You cannot have two anchors mapped to the same state."
+        }
+        val density = LocalDensity.current
+        state.ensureInit(anchors)
+        LaunchedEffect(anchors, state) {
+            val oldAnchors = state.anchors
+            state.anchors = anchors
+            state.resistance = resistance
+            state.thresholds = { a, b ->
+                val from = anchors.getValue(a)
+                val to = anchors.getValue(b)
+                with(thresholds(from, to)) { density.computeThreshold(a, b) }
+            }
+            with(density) { state.velocityThreshold = velocityThreshold.toPx() }
+            state.processNewAnchors(oldAnchors, anchors)
+        }
+
+        Modifier.draggable(
+            orientation = orientation,
+            enabled = enabled,
+            reverseDirection = reverseDirection,
+            interactionSource = interactionSource,
+            startDragImmediately = state.isAnimationRunning,
+            onDragStopped = { velocity -> launch { state.performFling(velocity) } },
+            state = state.draggableState
+        )
+    }
+
+/**
+ * Interface to compute a threshold between two anchors/states in a [swipeable].
+ *
+ * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
+ */
+@Stable
+interface ThresholdConfig {
+    /** Compute the value of the threshold (in pixels), once the values of the anchors are known. */
+    fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
+}
+
+/**
+ * A fixed threshold will be at an [offset] away from the first anchor.
+ *
+ * @param offset The offset (in dp) that the threshold will be at.
+ */
+@Immutable
+data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return fromValue + offset.toPx() * sign(toValue - fromValue)
+    }
+}
+
+/**
+ * A fractional threshold will be at a [fraction] of the way between the two anchors.
+ *
+ * @param fraction The fraction (between 0 and 1) that the threshold will be at.
+ */
+@Immutable
+data class FractionalThreshold(
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    private val fraction: Float
+) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return lerp(fromValue, toValue, fraction)
+    }
+}
+
+/**
+ * Specifies how resistance is calculated in [swipeable].
+ *
+ * There are two things needed to calculate resistance: the resistance basis determines how much
+ * overflow will be consumed to achieve maximum resistance, and the resistance factor determines the
+ * amount of resistance (the larger the resistance factor, the stronger the resistance).
+ *
+ * The resistance basis is usually either the size of the component which [swipeable] is applied to,
+ * or the distance between the minimum and maximum anchors. For a constructor in which the
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
+ *
+ * You may specify different resistance factors for each bound. Consider using one of the default
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user has
+ * run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe this
+ * right now. Also, you can set either factor to 0 to disable resistance at that bound.
+ *
+ * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
+ * @param factorAtMin The factor by which to scale the resistance at the minimum bound. Must not be
+ *   negative.
+ * @param factorAtMax The factor by which to scale the resistance at the maximum bound. Must not be
+ *   negative.
+ */
+@Immutable
+class ResistanceConfig(
+    /*@FloatRange(from = 0.0, fromInclusive = false)*/
+    val basis: Float,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMin: Float = StandardResistanceFactor,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMax: Float = StandardResistanceFactor
+) {
+    fun computeResistance(overflow: Float): Float {
+        val factor = if (overflow < 0) factorAtMin else factorAtMax
+        if (factor == 0f) return 0f
+        val progress = (overflow / basis).coerceIn(-1f, 1f)
+        return basis / factor * sin(progress * PI.toFloat() / 2)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ResistanceConfig) return false
+
+        if (basis != other.basis) return false
+        if (factorAtMin != other.factorAtMin) return false
+        if (factorAtMax != other.factorAtMax) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = basis.hashCode()
+        result = 31 * result + factorAtMin.hashCode()
+        result = 31 * result + factorAtMax.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
+    }
+}
+
+/**
+ * Given an offset x and a set of anchors, return a list of anchors:
+ * 1. [ ] if the set of anchors is empty,
+ * 2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x' is
+ *    x rounded to the exact value of the matching anchor,
+ * 3. [ min ] if min is the minimum anchor and x < min,
+ * 4. [ max ] if max is the maximum anchor and x > max, or
+ * 5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
+ */
+private fun findBounds(offset: Float, anchors: Set<Float>): List<Float> {
+    // Find the anchors the target lies between with a little bit of rounding error.
+    val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
+    val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
+
+    return when {
+        a == null ->
+            // case 1 or 3
+            listOfNotNull(b)
+        b == null ->
+            // case 4
+            listOf(a)
+        a == b ->
+            // case 2
+            // Can't return offset itself here since it might not be exactly equal
+            // to the anchor, despite being considered an exact match.
+            listOf(a)
+        else ->
+            // case 5
+            listOf(a, b)
+    }
+}
+
+private fun computeTarget(
+    offset: Float,
+    lastValue: Float,
+    anchors: Set<Float>,
+    thresholds: (Float, Float) -> Float,
+    velocity: Float,
+    velocityThreshold: Float
+): Float {
+    val bounds = findBounds(offset, anchors)
+    return when (bounds.size) {
+        0 -> lastValue
+        1 -> bounds[0]
+        else -> {
+            val lower = bounds[0]
+            val upper = bounds[1]
+            if (lastValue <= offset) {
+                // Swiping from lower to upper (positive).
+                if (velocity >= velocityThreshold) {
+                    return upper
+                } else {
+                    val threshold = thresholds(lower, upper)
+                    if (offset < threshold) lower else upper
+                }
+            } else {
+                // Swiping from upper to lower (negative).
+                if (velocity <= -velocityThreshold) {
+                    return lower
+                } else {
+                    val threshold = thresholds(upper, lower)
+                    if (offset > threshold) upper else lower
+                }
+            }
+        }
+    }
+}
+
+private fun <T> Map<Float, T>.getOffset(state: T): Float? {
+    return entries.firstOrNull { it.value == state }?.key
+}
+
+/** Contains useful defaults for [swipeable] and [SwipeableState]. */
+object SwipeableDefaults {
+    /** The default animation used by [SwipeableState]. */
+    val AnimationSpec = SpringSpec<Float>()
+
+    /** The default velocity threshold (1.8 dp per millisecond) used by [swipeable]. */
+    val VelocityThreshold = 125.dp
+
+    /** A stiff resistance factor which indicates that swiping isn't available right now. */
+    const val StiffResistanceFactor = 20f
+
+    /** A standard resistance factor which indicates that the user has run out of things to see. */
+    const val StandardResistanceFactor = 10f
+
+    /**
+     * The default resistance config used by [swipeable].
+     *
+     * This returns `null` if there is one anchor. If there are at least two anchors, it returns a
+     * [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+     */
+    fun resistanceConfig(
+        anchors: Set<Float>,
+        factorAtMin: Float = StandardResistanceFactor,
+        factorAtMax: Float = StandardResistanceFactor
+    ): ResistanceConfig? {
+        return if (anchors.size <= 1) {
+            null
+        } else {
+            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+            ResistanceConfig(basis, factorAtMin, factorAtMax)
+        }
+    }
+}
+
+// temp default nested scroll connection for swipeables which desire as an opt in
+// revisit in b/174756744 as all types will have their own specific connection probably
+internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
+    get() =
+        object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                val delta = available.toFloat()
+                return if (delta < 0 && source == NestedScrollSource.Drag) {
+                    performDrag(delta).toOffset()
+                } else {
+                    Offset.Zero
+                }
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                return if (source == NestedScrollSource.Drag) {
+                    performDrag(available.toFloat()).toOffset()
+                } else {
+                    Offset.Zero
+                }
+            }
+
+            override suspend fun onPreFling(available: Velocity): Velocity {
+                val toFling = Offset(available.x, available.y).toFloat()
+                return if (toFling < 0 && offset.value > minBound) {
+                    performFling(velocity = toFling)
+                    // since we go to the anchor with tween settling, consume all for the best UX
+                    available
+                } else {
+                    Velocity.Zero
+                }
+            }
+
+            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+                performFling(velocity = Offset(available.x, available.y).toFloat())
+                return available
+            }
+
+            private fun Float.toOffset(): Offset = Offset(0f, this)
+
+            private fun Offset.toFloat(): Float = this.y
+        }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
new file mode 100644
index 0000000..c1defb7
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.compose.ui.util
+
+import kotlin.math.roundToInt
+import kotlin.math.roundToLong
+
+// TODO(b/272311106): this is a fork from material. Unfork it when MathHelpers.kt reaches material3.
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Float, stop: Float, fraction: Float): Float {
+    return (1 - fraction) * start + fraction * stop
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Int, stop: Int, fraction: Float): Int {
+    return start + ((stop - start) * fraction.toDouble()).roundToInt()
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Long, stop: Long, fraction: Float): Long {
+    return start + ((stop - start) * fraction.toDouble()).roundToLong()
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index e253fb9..cc33745 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -21,8 +21,10 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is *not* available. */
 object ComposeFacade : BaseComposeFacade {
@@ -48,6 +50,14 @@
         throwComposeUnavailableError()
     }
 
+    override fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View {
+        throwComposeUnavailableError()
+    }
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1ea18fe..0e79b18 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -23,10 +23,13 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
+import com.android.systemui.multishade.ui.composable.MultiShade
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is available. */
 object ComposeFacade : BaseComposeFacade {
@@ -51,4 +54,21 @@
             setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
         }
     }
+
+    override fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View {
+        return ComposeView(context).apply {
+            setContent {
+                PlatformTheme {
+                    MultiShade(
+                        viewModel = viewModel,
+                        clock = clock,
+                    )
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
new file mode 100644
index 0000000..b9e38cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.detectVerticalDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.IntSize
+import com.android.systemui.R
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.statusbar.ui.composable.StatusBar
+import com.android.systemui.util.time.SystemClock
+
+@Composable
+fun MultiShade(
+    viewModel: MultiShadeViewModel,
+    clock: SystemClock,
+    modifier: Modifier = Modifier,
+) {
+    val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState()
+
+    // TODO(b/273298030): find a different way to get the height constraint from its parent.
+    BoxWithConstraints(modifier = modifier) {
+        val maxHeightPx = with(LocalDensity.current) { maxHeight.toPx() }
+
+        Scrim(
+            modifier = Modifier.fillMaxSize(),
+            remoteTouch = viewModel::onScrimTouched,
+            alpha = { viewModel.scrimAlpha.value },
+            isScrimEnabled = isScrimEnabled,
+        )
+        Shade(
+            viewModel = viewModel.leftShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier.align(Alignment.TopStart),
+        ) {
+            Column {
+                StatusBar()
+                Notifications()
+            }
+        }
+        Shade(
+            viewModel = viewModel.rightShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier.align(Alignment.TopEnd),
+        ) {
+            Column {
+                StatusBar()
+                QuickSettings()
+            }
+        }
+        Shade(
+            viewModel = viewModel.singleShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier,
+        ) {
+            Column {
+                StatusBar()
+                Notifications()
+                QuickSettings()
+            }
+        }
+    }
+}
+
+@Composable
+private fun Scrim(
+    remoteTouch: (ProxiedInputModel) -> Unit,
+    alpha: () -> Float,
+    isScrimEnabled: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    var size by remember { mutableStateOf(IntSize.Zero) }
+
+    Box(
+        modifier =
+            modifier
+                .graphicsLayer { this.alpha = alpha() }
+                .background(colorResource(R.color.opaque_scrim))
+                .fillMaxSize()
+                .onSizeChanged { size = it }
+                .then(
+                    if (isScrimEnabled) {
+                        Modifier.pointerInput(Unit) {
+                                detectTapGestures(onTap = { remoteTouch(ProxiedInputModel.OnTap) })
+                            }
+                            .pointerInput(Unit) {
+                                detectVerticalDragGestures(
+                                    onVerticalDrag = { change, dragAmount ->
+                                        remoteTouch(
+                                            ProxiedInputModel.OnDrag(
+                                                xFraction = change.position.x / size.width,
+                                                yDragAmountPx = dragAmount,
+                                            )
+                                        )
+                                    },
+                                    onDragEnd = { remoteTouch(ProxiedInputModel.OnDragEnd) },
+                                    onDragCancel = { remoteTouch(ProxiedInputModel.OnDragCancel) }
+                                )
+                            }
+                    } else {
+                        Modifier
+                    }
+                )
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
new file mode 100644
index 0000000..98ef57f9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.height
+import com.android.compose.swipeable.FixedThreshold
+import com.android.compose.swipeable.SwipeableState
+import com.android.compose.swipeable.ThresholdConfig
+import com.android.compose.swipeable.rememberSwipeableState
+import com.android.compose.swipeable.swipeable
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.ShadeViewModel
+import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
+
+/**
+ * Renders a shade (container and content).
+ *
+ * This should be allowed to grow to fill the width and height of its container.
+ *
+ * @param viewModel The view-model for this shade.
+ * @param currentTimeMillis A provider for the current time, in milliseconds.
+ * @param containerHeightPx The height of the container that this shade is being shown in, in
+ *   pixels.
+ * @param modifier The Modifier.
+ * @param content The content of the shade.
+ */
+@Composable
+fun Shade(
+    viewModel: ShadeViewModel,
+    currentTimeMillis: () -> Long,
+    containerHeightPx: Float,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit = {},
+) {
+    val isVisible: Boolean by viewModel.isVisible.collectAsState()
+    if (!isVisible) {
+        return
+    }
+
+    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+    ReportNonProxiedInput(viewModel, interactionSource)
+
+    val swipeableState = rememberSwipeableState(initialValue = ShadeState.FullyCollapsed)
+    HandleForcedCollapse(viewModel, swipeableState)
+    HandleProxiedInput(viewModel, swipeableState, currentTimeMillis)
+    ReportShadeExpansion(viewModel, swipeableState, containerHeightPx)
+
+    val isSwipingEnabled: Boolean by viewModel.isSwipingEnabled.collectAsState()
+    val collapseThreshold: Float by viewModel.swipeCollapseThreshold.collectAsState()
+    val expandThreshold: Float by viewModel.swipeExpandThreshold.collectAsState()
+
+    val width: ShadeViewModel.Size by viewModel.width.collectAsState()
+    val density = LocalDensity.current
+
+    val anchors: Map<Float, ShadeState> =
+        remember(containerHeightPx) { swipeableAnchors(containerHeightPx) }
+
+    ShadeContent(
+        shadeHeightPx = { swipeableState.offset.value },
+        overstretch = { swipeableState.overflow.value / containerHeightPx },
+        isSwipingEnabled = isSwipingEnabled,
+        swipeableState = swipeableState,
+        interactionSource = interactionSource,
+        anchors = anchors,
+        thresholds = { _, to ->
+            swipeableThresholds(
+                to = to,
+                swipeCollapseThreshold = collapseThreshold.fractionToDp(density, containerHeightPx),
+                swipeExpandThreshold = expandThreshold.fractionToDp(density, containerHeightPx),
+            )
+        },
+        modifier = modifier.shadeWidth(width, density),
+        content = content,
+    )
+}
+
+/**
+ * Draws the content of the shade.
+ *
+ * @param shadeHeightPx Provider for the current expansion of the shade, in pixels, where `0` is
+ *   fully collapsed.
+ * @param overstretch Provider for the current amount of vertical "overstretch" that the shade
+ *   should be rendered with. This is `0` or a positive number that is a percentage of the total
+ *   height of the shade when fully expanded. A value of `0` means that the shade is not stretched
+ *   at all.
+ * @param isSwipingEnabled Whether swiping inside the shade is enabled or not.
+ * @param swipeableState The state to use for the [swipeable] modifier, allowing external control in
+ *   addition to direct control (proxied user input in addition to non-proxied/direct user input).
+ * @param anchors A map of [ShadeState] keyed by the vertical position, in pixels, where that state
+ *   occurs; this is used to configure the [swipeable] modifier.
+ * @param thresholds Function that returns the [ThresholdConfig] for going from one [ShadeState] to
+ *   another. This controls how the [swipeable] decides which [ShadeState] to animate to once the
+ *   user lets go of the shade; e.g. does it animate to fully collapsed or fully expanded.
+ * @param content The content to render inside the shade.
+ * @param modifier The [Modifier].
+ */
+@Composable
+private fun ShadeContent(
+    shadeHeightPx: () -> Float,
+    overstretch: () -> Float,
+    isSwipingEnabled: Boolean,
+    swipeableState: SwipeableState<ShadeState>,
+    interactionSource: MutableInteractionSource,
+    anchors: Map<Float, ShadeState>,
+    thresholds: (from: ShadeState, to: ShadeState) -> ThresholdConfig,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit = {},
+) {
+    Surface(
+        shape = RoundedCornerShape(32.dp),
+        modifier =
+            modifier
+                .padding(12.dp)
+                .fillMaxWidth()
+                .height { shadeHeightPx().roundToInt() }
+                .graphicsLayer {
+                    // Applies the vertical over-stretching of the shade content that may happen if
+                    // the user keep dragging down when the shade is already fully-expanded.
+                    transformOrigin = transformOrigin.copy(pivotFractionY = 0f)
+                    this.scaleY = 1 + overstretch().coerceAtLeast(0f)
+                }
+                .swipeable(
+                    enabled = isSwipingEnabled,
+                    state = swipeableState,
+                    interactionSource = interactionSource,
+                    anchors = anchors,
+                    thresholds = thresholds,
+                    orientation = Orientation.Vertical,
+                ),
+        content = content,
+    )
+}
+
+/** Funnels current shade expansion values into the view-model. */
+@Composable
+private fun ReportShadeExpansion(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+    containerHeightPx: Float,
+) {
+    LaunchedEffect(swipeableState.offset, containerHeightPx) {
+        snapshotFlow { swipeableState.offset.value / containerHeightPx }
+            .collect { expansion -> viewModel.onExpansionChanged(expansion) }
+    }
+}
+
+/** Funnels drag gesture start and end events into the view-model. */
+@Composable
+private fun ReportNonProxiedInput(
+    viewModel: ShadeViewModel,
+    interactionSource: InteractionSource,
+) {
+    LaunchedEffect(interactionSource) {
+        interactionSource.interactions.collect {
+            when (it) {
+                is DragInteraction.Start -> {
+                    viewModel.onDragStarted()
+                }
+                is DragInteraction.Stop -> {
+                    viewModel.onDragEnded()
+                }
+            }
+        }
+    }
+}
+
+/** When told to force collapse, collapses the shade. */
+@Composable
+private fun HandleForcedCollapse(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+) {
+    LaunchedEffect(viewModel) {
+        viewModel.isForceCollapsed.collect {
+            launch { swipeableState.animateTo(ShadeState.FullyCollapsed) }
+        }
+    }
+}
+
+/**
+ * Handles proxied input (input originating outside of the UI of the shade) by driving the
+ * [SwipeableState] accordingly.
+ */
+@Composable
+private fun HandleProxiedInput(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+    currentTimeMillis: () -> Long,
+) {
+    val velocityTracker: VelocityTracker = remember { VelocityTracker() }
+    LaunchedEffect(viewModel) {
+        viewModel.proxiedInput.collect {
+            when (it) {
+                is ProxiedInputModel.OnDrag -> {
+                    velocityTracker.addPosition(
+                        timeMillis = currentTimeMillis.invoke(),
+                        position = Offset(0f, it.yDragAmountPx),
+                    )
+                    swipeableState.performDrag(it.yDragAmountPx)
+                }
+                is ProxiedInputModel.OnDragEnd -> {
+                    launch {
+                        val velocity = velocityTracker.calculateVelocity().y
+                        velocityTracker.resetTracking()
+                        // We use a VelocityTracker to keep a record of how fast the pointer was
+                        // moving such that we know how far to fling the shade when the gesture
+                        // ends. Flinging the SwipeableState using performFling is required after
+                        // one or more calls to performDrag such that the swipeable settles into one
+                        // of the states. Without doing that, the shade would remain unmoving in an
+                        // in-between state on the screen.
+                        swipeableState.performFling(velocity)
+                    }
+                }
+                is ProxiedInputModel.OnDragCancel -> {
+                    launch {
+                        velocityTracker.resetTracking()
+                        swipeableState.animateTo(swipeableState.progress.from)
+                    }
+                }
+                else -> Unit
+            }
+        }
+    }
+}
+
+/**
+ * Converts the [Float] (which is assumed to be a fraction between `0` and `1`) to a value in dp.
+ *
+ * @param density The [Density] of the display.
+ * @param wholePx The whole amount that the given [Float] is a fraction of.
+ * @return The dp size that's a fraction of the whole amount.
+ */
+private fun Float.fractionToDp(density: Density, wholePx: Float): Dp {
+    return with(density) { (this@fractionToDp * wholePx).toDp() }
+}
+
+private fun Modifier.shadeWidth(
+    size: ShadeViewModel.Size,
+    density: Density,
+): Modifier {
+    return then(
+        when (size) {
+            is ShadeViewModel.Size.Fraction -> Modifier.fillMaxWidth(size.fraction)
+            is ShadeViewModel.Size.Pixels -> Modifier.width(with(density) { size.pixels.toDp() })
+        }
+    )
+}
+
+/** Returns the pixel positions for each of the supported shade states. */
+private fun swipeableAnchors(containerHeightPx: Float): Map<Float, ShadeState> {
+    return mapOf(
+        0f to ShadeState.FullyCollapsed,
+        containerHeightPx to ShadeState.FullyExpanded,
+    )
+}
+
+/**
+ * Returns the [ThresholdConfig] for how far the shade should be expanded or collapsed such that it
+ * actually completes the expansion or collapse after the user lifts their pointer.
+ */
+private fun swipeableThresholds(
+    to: ShadeState,
+    swipeExpandThreshold: Dp,
+    swipeCollapseThreshold: Dp,
+): ThresholdConfig {
+    return FixedThreshold(
+        when (to) {
+            ShadeState.FullyExpanded -> swipeExpandThreshold
+            ShadeState.FullyCollapsed -> swipeCollapseThreshold
+        }
+    )
+}
+
+/** Enumerates the shade UI states for [SwipeableState]. */
+private enum class ShadeState {
+    FullyCollapsed,
+    FullyExpanded,
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
new file mode 100644
index 0000000..ca91b8a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.notifications.ui.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun Notifications(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272779828): implement.
+    Column(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+    ) {
+        Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Spacer(modifier = Modifier.weight(1f))
+        Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
new file mode 100644
index 0000000..665d6dd
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.qs.footer.ui.compose
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun QuickSettings(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272780058): implement.
+    Column(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+    ) {
+        Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Spacer(modifier = Modifier.weight(1f))
+        Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
new file mode 100644
index 0000000..f514ab4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun StatusBar(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272780101): implement.
+    Row(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).padding(4.dp),
+        horizontalArrangement = Arrangement.Center,
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Text("Status bar")
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 1d28c63..c0b69c1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -189,10 +189,12 @@
 
         /** Get the text for secondaryLabel. */
         public String getSecondaryLabel(String stateText) {
-            if (TextUtils.isEmpty(secondaryLabel)) {
+            // Use a local reference as the value might change from other threads
+            CharSequence localSecondaryLabel = secondaryLabel;
+            if (TextUtils.isEmpty(localSecondaryLabel)) {
                 return stateText;
             }
-            return secondaryLabel.toString();
+            return localSecondaryLabel.toString();
         }
 
         public boolean copyTo(State other) {
diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml
index 7a628bb..642582c 100644
--- a/packages/SystemUI/res/drawable/ic_important_outline.xml
+++ b/packages/SystemUI/res/drawable/ic_important_outline.xml
@@ -20,6 +20,7 @@
         android:height="24dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0"
+        android:autoMirrored="true"
         android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="@android:color/white"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 311990c..e5cd0c5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -844,4 +844,44 @@
 
     <!-- Configuration to set Learn more in device logs as URL link -->
     <bool name="log_access_confirmation_learn_more_as_link">true</bool>
+
+    <!-- [START] MULTI SHADE -->
+    <!-- Whether the device should use dual shade. If false, the device uses single shade. -->
+    <bool name="dual_shade_enabled">true</bool>
+    <!--
+    When in dual shade, where should the horizontal split be on the screen to help determine whether
+    the user is pulling down the left shade or the right shade. Must be between 0.0 and 1.0,
+    inclusive. In other words: how much of the left-hand side of the screen, when pulled down on,
+    would reveal the left-hand side shade.
+
+    More concretely:
+    A value of 0.67 means that the left two-thirds of the screen are dedicated to the left-hand side
+    shade and the remaining one-third of the screen on the right is dedicated to the right-hand side
+    shade.
+    -->
+    <dimen name="dual_shade_split_fraction">0.67</dimen>
+    <!-- Width of the left-hand side shade. -->
+    <dimen name="left_shade_width">436dp</dimen>
+    <!-- Width of the right-hand side shade. -->
+    <dimen name="right_shade_width">436dp</dimen>
+    <!--
+    Opaque version of the scrim that shows up behind dual shades. The alpha channel is driven
+    programmatically.
+    -->
+    <color name="opaque_scrim">#D9D9D9</color>
+    <!-- Maximum opacity when the scrim that shows up behind the dual shades is fully visible. -->
+    <dimen name="dual_shade_scrim_alpha">0.1</dimen>
+    <!--
+    The amount that the user must swipe down when the shade is fully collapsed to automatically
+    expand once the user lets go of the shade. If the user swipes less than this amount, the shade
+    will automatically revert back to fully collapsed once the user stops swiping.
+    -->
+    <dimen name="shade_swipe_expand_threshold">0.5</dimen>
+    <!--
+    The amount that the user must swipe up when the shade is fully expanded to automatically
+    collapse once the user lets go of the shade. If the user swipes less than this amount, the shade
+    will automatically revert back to fully expanded once the user stops swiping.
+    -->
+    <dimen name="shade_swipe_collapse_threshold">0.5</dimen>
+    <!-- [END] MULTI SHADE -->
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 6f7d66d..58e7747 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -321,7 +321,9 @@
                     } else {
                         // We are receiving new opening tasks, so convert to onTasksAppeared.
                         targets[i] = TransitionUtil.newTarget(change, layer, info, t, mLeashMap);
-                        t.reparent(targets[i].leash, mInfo.getRootLeash());
+                        // reparent into the original `mInfo` since that's where we are animating.
+                        final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+                        t.reparent(targets[i].leash, mInfo.getRoot(rootIdx).getLeash());
                         t.setLayer(targets[i].leash, layer);
                         mOpeningTasks.add(new TaskState(change, targets[i].leash));
                     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 0e5f8c1..553453d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -18,13 +18,9 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
@@ -33,22 +29,10 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
 
-import java.lang.ref.WeakReference;
-
 /***
  * Manages a number of views inside of the given layout. See below for a list of widgets.
  */
 public abstract class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
-    /** Handler token posted with accessibility announcement runnables. */
-    private static final Object ANNOUNCE_TOKEN = new Object();
-
-    /**
-     * Delay before speaking an accessibility announcement. Used to prevent
-     * lift-to-type from interrupting itself.
-     */
-    private static final long ANNOUNCEMENT_DELAY = 250;
-
-    private final Handler mHandler;
 
     private CharSequence mMessage;
     private boolean mIsVisible;
@@ -65,7 +49,6 @@
         super(context, attrs);
         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
 
-        mHandler = new Handler(Looper.myLooper());
         onThemeChanged();
     }
 
@@ -127,9 +110,6 @@
     private void securityMessageChanged(CharSequence message) {
         mMessage = message;
         update();
-        mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
-        mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
-                (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
     }
 
     private void clearMessage() {
@@ -156,25 +136,4 @@
 
     /** Set the text color */
     protected abstract void updateTextColor();
-
-    /**
-     * Runnable used to delay accessibility announcements.
-     */
-    private static class AnnounceRunnable implements Runnable {
-        private final WeakReference<View> mHost;
-        private final CharSequence mTextToAnnounce;
-
-        AnnounceRunnable(View host, CharSequence textToAnnounce) {
-            mHost = new WeakReference<View>(host);
-            mTextToAnnounce = textToAnnounce;
-        }
-
-        @Override
-        public void run() {
-            final View host = mHost.get();
-            if (host != null) {
-                host.announceForAccessibility(mTextToAnnounce);
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 6a92162..c1896fc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,11 +18,17 @@
 
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.util.ViewController;
 
+import java.lang.ref.WeakReference;
+
 import javax.inject.Inject;
 
 /**
@@ -31,8 +37,14 @@
  */
 public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
         extends ViewController<T> {
+    /**
+     * Delay before speaking an accessibility announcement. Used to prevent
+     * lift-to-type from interrupting itself.
+     */
+    private static final long ANNOUNCEMENT_DELAY = 250;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
+    private final AnnounceRunnable mAnnounceRunnable;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
         public void onFinishedGoingToSleep(int why) {
@@ -68,6 +80,7 @@
 
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
+        mAnnounceRunnable = new AnnounceRunnable(mView);
     }
 
     @Override
@@ -100,6 +113,12 @@
      */
     public void setMessage(CharSequence s, boolean animate) {
         mView.setMessage(s, animate);
+        CharSequence msg = mView.getText();
+        if (!TextUtils.isEmpty(msg)) {
+            mView.removeCallbacks(mAnnounceRunnable);
+            mAnnounceRunnable.setTextToAnnounce(msg);
+            mView.postDelayed(mAnnounceRunnable, ANNOUNCEMENT_DELAY);
+        }
     }
 
     public void setMessage(int resId) {
@@ -134,4 +153,30 @@
                     view, mKeyguardUpdateMonitor, mConfigurationController);
         }
     }
+
+    /**
+     * Runnable used to delay accessibility announcements.
+     */
+    @VisibleForTesting
+    public static class AnnounceRunnable implements Runnable {
+        private final WeakReference<View> mHost;
+        private CharSequence mTextToAnnounce;
+
+        AnnounceRunnable(View host) {
+            mHost = new WeakReference<>(host);
+        }
+
+        /** Sets the text to announce. */
+        public void setTextToAnnounce(CharSequence textToAnnounce) {
+            mTextToAnnounce = textToAnnounce;
+        }
+
+        @Override
+        public void run() {
+            final View host = mHost.get();
+            if (host != null) {
+                host.announceForAccessibility(mTextToAnnounce);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 98866c6..7255383 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -1066,23 +1066,28 @@
     }
 
     private void reloadColors() {
-        reinflateViewFlipper();
-        mView.reloadColors();
+        reinflateViewFlipper(() -> mView.reloadColors());
     }
 
     /** Handles density or font scale changes. */
     private void onDensityOrFontScaleChanged() {
-        reinflateViewFlipper();
-        mView.onDensityOrFontScaleChanged();
+        reinflateViewFlipper(() -> mView.onDensityOrFontScaleChanged());
     }
 
     /**
      * Reinflate the view flipper child view.
      */
-    public void reinflateViewFlipper() {
+    public void reinflateViewFlipper(
+            KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener) {
         mSecurityViewFlipperController.clearViews();
-        mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
-                mKeyguardSecurityCallback);
+        if (mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)) {
+            mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode,
+                    mKeyguardSecurityCallback, onViewInflatedListener);
+        } else {
+            mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
+                    mKeyguardSecurityCallback);
+            onViewInflatedListener.onViewInflated();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 39b567f..68e1dd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -19,11 +19,16 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 
+import androidx.annotation.Nullable;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardInputViewController.Factory;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.util.ViewController;
 
 import java.util.ArrayList;
@@ -44,18 +49,24 @@
     private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
             new ArrayList<>();
     private final LayoutInflater mLayoutInflater;
+    private final AsyncLayoutInflater mAsyncLayoutInflater;
     private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
     private final Factory mKeyguardSecurityViewControllerFactory;
+    private final FeatureFlags mFeatureFlags;
 
     @Inject
     protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
             LayoutInflater layoutInflater,
+            AsyncLayoutInflater asyncLayoutInflater,
             KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
-            EmergencyButtonController.Factory emergencyButtonControllerFactory) {
+            EmergencyButtonController.Factory emergencyButtonControllerFactory,
+            FeatureFlags featureFlags) {
         super(view);
         mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
         mLayoutInflater = layoutInflater;
         mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
+        mAsyncLayoutInflater = asyncLayoutInflater;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -92,13 +103,12 @@
             }
         }
 
-        if (childController == null
+        if (!mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER) && childController == null
                 && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) {
-
             int layoutId = getLayoutIdFor(securityMode);
             KeyguardInputView view = null;
             if (layoutId != 0) {
-                if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
+                if (DEBUG) Log.v(TAG, "inflating on main thread id = " + layoutId);
                 view = (KeyguardInputView) mLayoutInflater.inflate(
                         layoutId, mView, false);
                 mView.addView(view);
@@ -119,6 +129,36 @@
         return childController;
     }
 
+    /**
+     * Asynchronously inflate view and then add it to view flipper on the main thread when complete.
+     *
+     * OnInflateFinishedListener will be called on the main thread.
+     *
+     * @param securityMode
+     * @param keyguardSecurityCallback
+     */
+    public void asynchronouslyInflateView(SecurityMode securityMode,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            @Nullable OnViewInflatedListener onViewInflatedListener) {
+        int layoutId = getLayoutIdFor(securityMode);
+        if (layoutId != 0) {
+            if (DEBUG) Log.v(TAG, "inflating on bg thread id = " + layoutId);
+            mAsyncLayoutInflater.inflate(layoutId, mView,
+                    (view, resId, parent) -> {
+                        mView.addView(view);
+                        KeyguardInputViewController<KeyguardInputView> childController =
+                                mKeyguardSecurityViewControllerFactory.create(
+                                        (KeyguardInputView) view, securityMode,
+                                        keyguardSecurityCallback);
+                        childController.init();
+                        mChildren.add(childController);
+                        if (onViewInflatedListener != null) {
+                            onViewInflatedListener.onViewInflated();
+                        }
+                    });
+        }
+    }
+
     private int getLayoutIdFor(SecurityMode securityMode) {
         switch (securityMode) {
             case Pattern: return R.layout.keyguard_pattern_view;
@@ -162,4 +202,10 @@
             return 0;
         }
     }
+
+    /** Listener to when view has finished inflation. */
+    public interface OnViewInflatedListener {
+        /** Notifies that view has been inflated */
+        void onViewInflated();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 9c847be..08236b7 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -61,3 +61,8 @@
 ##       4: SYSTEM_REGISTER_USER     System sysui registers user's callbacks
 ##       5: SYSTEM_UNREGISTER_USER   System sysui unregisters user's callbacks (after death)
 36060 sysui_recents_connection (type|1),(user|1)
+
+# ---------------------------
+# KeyguardViewMediator.java
+# ---------------------------
+36080 sysui_keyguard (isOccluded|1),(animate|1)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c31d45f..4aa985b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -198,32 +198,36 @@
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (mCurrentDialog != null
-                    && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                 String reason = intent.getStringExtra("reason");
                 reason = (reason != null) ? reason : "unknown";
-                Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason);
-
-                mCurrentDialog.dismissWithoutCallback(true /* animate */);
-                mCurrentDialog = null;
-
-                for (Callback cb : mCallbacks) {
-                    cb.onBiometricPromptDismissed();
-                }
-
-                try {
-                    if (mReceiver != null) {
-                        mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
-                                null /* credentialAttestation */);
-                        mReceiver = null;
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Remote exception", e);
-                }
+                closeDioalog(reason);
             }
         }
     };
 
+    private void closeDioalog(String reason) {
+        if (isShowing()) {
+            Log.i(TAG, "Close BP, reason :" + reason);
+            mCurrentDialog.dismissWithoutCallback(true /* animate */);
+            mCurrentDialog = null;
+
+            for (Callback cb : mCallbacks) {
+                cb.onBiometricPromptDismissed();
+            }
+
+            try {
+                if (mReceiver != null) {
+                    mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                            null /* credentialAttestation */);
+                    mReceiver = null;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote exception", e);
+            }
+        }
+    }
+
     private void cancelIfOwnerIsNotInForeground() {
         mExecution.assertIsMainThread();
         if (mCurrentDialog != null) {
@@ -546,6 +550,11 @@
         }
     }
 
+    @Override
+    public void handleShowGlobalActionsMenu() {
+        closeDioalog("PowerMenu shown");
+    }
+
     /**
      * @return where the UDFPS exists on the screen in pixels in portrait mode.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index c0f8549..4173bdc 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -21,8 +21,10 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /**
  * A facade to interact with Compose, when it is available.
@@ -57,4 +59,11 @@
         viewModel: FooterActionsViewModel,
         qsVisibilityLifecycleOwner: LifecycleOwner,
     ): View
+
+    /** Create a [View] to represent [viewModel] on screen. */
+    fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index 06d4a08..ce0f2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -155,17 +155,18 @@
         d.show()
     }
 
-    private fun turnOnSettingSecurely(settings: List<String>) {
+    private fun turnOnSettingSecurely(settings: List<String>, onComplete: () -> Unit) {
         val action =
             ActivityStarter.OnDismissAction {
                 settings.forEach { setting ->
                     secureSettings.putIntForUser(setting, 1, userTracker.userId)
                 }
+                onComplete()
                 true
             }
         activityStarter.dismissKeyguardThenExecute(
             action,
-            /* cancel */ null,
+            /* cancel */ onComplete,
             /* afterKeyguardGone */ true
         )
     }
@@ -186,7 +187,11 @@
                 if (!showDeviceControlsInLockscreen) {
                     settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
                 }
-                turnOnSettingSecurely(settings)
+                // If we are toggling the flag, we want to call onComplete after the keyguard is
+                // dismissed (and the setting is turned on), to pass the correct value.
+                turnOnSettingSecurely(settings, onComplete)
+            } else {
+                onComplete()
             }
             if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
                 prefs
@@ -194,7 +199,6 @@
                     .putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
                     .apply()
             }
-            onComplete()
         }
 
         override fun onCancel(dialog: DialogInterface?) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b054c7e..0be3bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -98,6 +98,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.textclassifier.TextClassificationManager;
 
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 import androidx.core.app.NotificationManagerCompat;
 
 import com.android.internal.app.IBatteryStats;
@@ -395,6 +396,13 @@
         return LayoutInflater.from(context);
     }
 
+    /** */
+    @Provides
+    @Singleton
+    public AsyncLayoutInflater provideAsyncLayoutInflater(Context context) {
+        return new AsyncLayoutInflater(context);
+    }
+
     @Provides
     static MediaProjectionManager provideMediaProjectionManager(Context context) {
         return context.getSystemService(MediaProjectionManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
index 244212b..1702eac 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -75,6 +75,10 @@
                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
                 settingsObserver,
                 UserHandle.myUserId());
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
+                settingsObserver,
+                UserHandle.myUserId());
         settingsObserver.onChange(false);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 9b954f5f..6808142 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -51,6 +51,7 @@
     int DREAM_MEDIA_COMPLICATION_WEIGHT = 0;
     int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4;
     int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3;
+    int DREAM_WEATHER_COMPLICATION_WEIGHT = 0;
 
     /**
      * Provides layout parameters for the clock time complication.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 63f63a5..78e132f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -30,14 +30,15 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
+import com.android.systemui.smartspace.dagger.SmartspaceModule
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
 import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
 import com.android.systemui.util.concurrency.Execution
-import java.lang.RuntimeException
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -56,13 +57,16 @@
     @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
     @Named(DREAM_SMARTSPACE_TARGET_FILTER)
     private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
-    @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
+    @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
+    @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
+    optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
 ) {
     companion object {
         private const val TAG = "DreamSmartspaceCtrlr"
     }
 
     private var session: SmartspaceSession? = null
+    private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
 
@@ -116,31 +120,54 @@
     private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
         execution.assertIsMainThread()
 
+        // The weather data plugin takes unfiltered targets and performs the filtering internally.
+        weatherPlugin?.onTargetsAvailable(targets)
+
         onTargetsAvailableUnfiltered(targets)
         val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
         plugin?.onTargetsAvailable(filteredTargets)
     }
 
     /**
+     * Constructs the weather view with custom layout and connects it to the weather plugin.
+     */
+    fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
+        return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
+    }
+
+    /**
      * Constructs the smartspace view and connects it to the smartspace service.
      */
     fun buildAndConnectView(parent: ViewGroup): View? {
+        return buildAndConnectViewWithPlugin(parent, plugin, null)
+    }
+
+    private fun buildAndConnectViewWithPlugin(
+        parent: ViewGroup,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+        customView: View?
+    ): View? {
         execution.assertIsMainThread()
 
         if (!precondition.conditionsMet()) {
             throw RuntimeException("Cannot build view when not enabled")
         }
 
-        val view = buildView(parent)
+        val view = buildView(parent, smartspaceDataPlugin, customView)
 
         connectSession()
 
         return view
     }
 
-    private fun buildView(parent: ViewGroup): View? {
-        return if (plugin != null) {
-            var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
+    private fun buildView(
+        parent: ViewGroup,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+        customView: View?
+    ): View? {
+        return if (smartspaceDataPlugin != null) {
+            val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
+                stateChangeListener, customView)
                 .getView()
             if (view !is View) {
                 return null
@@ -157,7 +184,10 @@
     }
 
     private fun connectSession() {
-        if (plugin == null || session != null || !hasActiveSessionListeners()) {
+        if (plugin == null && weatherPlugin == null) {
+            return
+        }
+        if (session != null || !hasActiveSessionListeners()) {
             return
         }
 
@@ -166,13 +196,14 @@
         }
 
         val newSession = smartspaceManager.createSmartspaceSession(
-            SmartspaceConfig.Builder(context, "dream").build()
+            SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
         )
         Log.d(TAG, "Starting smartspace session for dream")
         newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
-        plugin.registerSmartspaceEventNotifier {
+        weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+        plugin?.registerSmartspaceEventNotifier {
                 e ->
             session?.notifySmartspaceEvent(e)
         }
@@ -199,22 +230,47 @@
 
         session = null
 
+        weatherPlugin?.registerSmartspaceEventNotifier(null)
+        weatherPlugin?.onTargetsAvailable(emptyList())
+
         plugin?.registerSmartspaceEventNotifier(null)
         plugin?.onTargetsAvailable(emptyList())
         Log.d(TAG, "Ending smartspace session for dream")
     }
 
     fun addListener(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, plugin)
+    }
+
+    fun removeListener(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, plugin)
+    }
+
+    fun addListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, weatherPlugin)
+    }
+
+    fun removeListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, weatherPlugin)
+    }
+
+    private fun addAndRegisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
         execution.assertIsMainThread()
-        plugin?.registerListener(listener)
+        smartspaceDataPlugin?.registerListener(listener)
         listeners.add(listener)
 
         connectSession()
     }
 
-    fun removeListener(listener: SmartspaceTargetListener) {
+    private fun removeAndUnregisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
         execution.assertIsMainThread()
-        plugin?.unregisterListener(listener)
+        smartspaceDataPlugin?.unregisterListener(listener)
         listeners.remove(listener)
         disconnect()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b611f46..661b2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -244,6 +244,11 @@
     @JvmField
     val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay")
 
+    // TODO(b/271460958): Tracking Bug
+    @JvmField
+    val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = unreleasedFlag(405,
+        "show_weather_complication_on_dream_overlay")
+
     // 500 - quick settings
 
     val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
@@ -591,8 +596,7 @@
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
     // TODO(b/267162944): Tracking bug
-    @JvmField
-    val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model", teamfood = true)
+    @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = releasedFlag(1702, "clipboard_data_model")
 
     // 1800 - shade container
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 377a136..5ecc00f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -118,6 +118,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.Interpolators;
@@ -1849,6 +1850,8 @@
     private void handleSetOccluded(boolean isOccluded, boolean animate) {
         Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
         Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
+        EventLogTags.writeSysuiKeyguard(isOccluded ? 1 : 0, animate ? 1 : 0);
+
         mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
 
         synchronized (KeyguardViewMediator.this) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 450fa14..82be009 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -176,10 +176,10 @@
             return;
         }
 
-        final Intent credential = getKeyguardManager()
+        final Intent confirmCredentialIntent = getKeyguardManager()
                 .createConfirmDeviceCredentialIntent(null, null, getTargetUserId(),
                 true /* disallowBiometricsIfPolicyExists */);
-        if (credential == null) {
+        if (confirmCredentialIntent == null) {
             return;
         }
 
@@ -193,14 +193,18 @@
                 PendingIntent.FLAG_IMMUTABLE, options.toBundle());
 
         if (target != null) {
-            credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
+            confirmCredentialIntent.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
         }
 
+        // WorkLockActivity is started as a task overlay, so unless credential confirmation is also
+        // started as an overlay, it won't be visible.
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchTaskId(getTaskId());
         launchOptions.setTaskOverlay(true /* taskOverlay */, true /* canResume */);
+        // Propagate it in case more than one activity is launched.
+        confirmCredentialIntent.putExtra(KeyguardManager.EXTRA_FORCE_TASK_OVERLAY, true);
 
-        startActivityForResult(credential, REQUEST_CODE_CONFIRM_CREDENTIALS,
+        startActivityForResult(confirmCredentialIntent, REQUEST_CODE_CONFIRM_CREDENTIALS,
                 launchOptions.toBundle());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index d716784..5fcf105 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.ActivityStarter
 import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
@@ -112,16 +111,18 @@
                     launch {
                         viewModel.show.collect {
                             // Reset Security Container entirely.
-                            view.visibility = View.VISIBLE
-                            securityContainerController.onBouncerVisibilityChanged(
-                                /* isVisible= */ true
-                            )
-                            securityContainerController.reinflateViewFlipper()
-                            securityContainerController.showPrimarySecurityScreen(
-                                /* turningOff= */ false
-                            )
-                            securityContainerController.appear()
-                            securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                            securityContainerController.reinflateViewFlipper {
+                                // Reset Security Container entirely.
+                                view.visibility = View.VISIBLE
+                                securityContainerController.onBouncerVisibilityChanged(
+                                    /* isVisible= */ true
+                                )
+                                securityContainerController.showPrimarySecurityScreen(
+                                    /* turningOff= */ false
+                                )
+                                securityContainerController.appear()
+                                securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                            }
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 50722d5..6d95882 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -26,18 +26,21 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 @SysUISingleton
 class KeyguardRemotePreviewManager
 @Inject
 constructor(
     private val previewRendererFactory: KeyguardPreviewRendererFactory,
+    @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val backgroundHandler: Handler,
 ) {
@@ -55,7 +58,13 @@
 
             // Destroy any previous renderer associated with this token.
             activePreviews[renderer.hostToken]?.let { destroyObserver(it) }
-            observer = PreviewLifecycleObserver(renderer, mainDispatcher, ::destroyObserver)
+            observer =
+                PreviewLifecycleObserver(
+                    renderer,
+                    applicationScope,
+                    mainDispatcher,
+                    ::destroyObserver,
+                )
             activePreviews[renderer.hostToken] = observer
             renderer.render()
             renderer.hostToken?.linkToDeath(observer, 0)
@@ -92,13 +101,18 @@
 
     private class PreviewLifecycleObserver(
         private val renderer: KeyguardPreviewRenderer,
+        private val scope: CoroutineScope,
         private val mainDispatcher: CoroutineDispatcher,
         private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
     ) : Handler.Callback, IBinder.DeathRecipient {
 
-        private var isDestroyed = false
+        private var isDestroyedOrDestroying = false
 
         override fun handleMessage(message: Message): Boolean {
+            if (isDestroyedOrDestroying) {
+                return true
+            }
+
             when (message.what) {
                 KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
                     message.data
@@ -118,14 +132,14 @@
         }
 
         fun onDestroy(): IBinder? {
-            if (isDestroyed) {
+            if (isDestroyedOrDestroying) {
                 return null
             }
 
-            isDestroyed = true
+            isDestroyedOrDestroying = true
             val hostToken = renderer.hostToken
             hostToken?.unlinkToDeath(this, 0)
-            runBlocking(mainDispatcher) { renderer.destroy() }
+            scope.launch(mainDispatcher) { renderer.destroy() }
             return hostToken
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 68910c6..0656c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -70,7 +70,7 @@
     /** Observe whether we should update fps is showing. */
     val shouldUpdateSideFps: Flow<Unit> =
         merge(
-            interactor.startingToHide,
+            interactor.hide,
             interactor.show,
             interactor.startingDisappearAnimation.filterNotNull().map {}
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index a31c1e5..00e5aac 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -519,16 +519,15 @@
                 mLogger.logTapContentView(mUid, mPackageName, mInstanceId);
                 logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
 
-                // See StatusBarNotificationActivityStarter#onNotificationClicked
                 boolean showOverLockscreen = mKeyguardStateController.isShowing()
-                        && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(),
+                        && mActivityIntentHelper.wouldPendingShowOverLockscreen(clickIntent,
                         mLockscreenUserManager.getCurrentUserId());
-
                 if (showOverLockscreen) {
-                    mActivityStarter.startActivity(clickIntent.getIntent(),
-                            /* dismissShade */ true,
-                            /* animationController */ null,
-                            /* showOverLockscreenWhenLocked */ true);
+                    try {
+                        clickIntent.send();
+                    } catch (PendingIntent.CanceledException e) {
+                        Log.e(TAG, "Pending intent for " + key + " was cancelled");
+                    }
                 } else {
                     mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
                             buildLaunchAnimatorController(mMediaViewHolder.getPlayer()));
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 00e9a79..b71a9193 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,17 +16,16 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
 import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -296,6 +295,8 @@
                             && mController.isAdvancedLayoutSupported()) {
                         //If device is connected and there's other selectable devices, layout as
                         // one of selected devices.
+                        updateTitleIcon(R.drawable.media_output_icon_volume,
+                                mController.getColorItemContent());
                         boolean isDeviceDeselectable = isDeviceIncluded(
                                 mController.getDeselectableMediaDevice(), device);
                         updateGroupableCheckBox(true, isDeviceDeselectable, device);
@@ -371,7 +372,8 @@
             mEndClickIcon.setOnClickListener(null);
             mEndTouchArea.setOnClickListener(null);
             updateEndClickAreaColor(mController.getColorSeekbarProgress());
-            mEndClickIcon.setColorFilter(mController.getColorItemContent());
+            mEndClickIcon.setImageTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
             mEndClickIcon.setOnClickListener(
                     v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
             mEndTouchArea.setOnClickListener(v -> mCheckBox.performClick());
@@ -379,8 +381,8 @@
 
         public void updateEndClickAreaColor(int color) {
             if (mController.isAdvancedLayoutSupported()) {
-                mEndTouchArea.getBackground().setColorFilter(
-                        new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
+                mEndTouchArea.setBackgroundTintList(
+                        ColorStateList.valueOf(color));
             }
         }
 
@@ -394,22 +396,22 @@
         private void updateConnectionFailedStatusIcon() {
             mStatusIcon.setImageDrawable(
                     mContext.getDrawable(R.drawable.media_output_status_failed));
-            mStatusIcon.setColorFilter(mController.getColorItemContent());
+            mStatusIcon.setImageTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
         }
 
         private void updateDeviceStatusIcon(Drawable drawable) {
             mStatusIcon.setImageDrawable(drawable);
-            mStatusIcon.setColorFilter(mController.getColorItemContent());
+            mStatusIcon.setImageTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
             if (drawable instanceof AnimatedVectorDrawable) {
                 ((AnimatedVectorDrawable) drawable).start();
             }
         }
 
         private void updateProgressBarColor() {
-            mProgressBar.getIndeterminateDrawable().setColorFilter(
-                    new PorterDuffColorFilter(
-                            mController.getColorItemContent(),
-                            PorterDuff.Mode.SRC_IN));
+            mProgressBar.getIndeterminateDrawable().setTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
         }
 
         public void updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable) {
@@ -419,9 +421,8 @@
             mEndTouchArea.setImportantForAccessibility(
                     View.IMPORTANT_FOR_ACCESSIBILITY_YES);
             if (mController.isAdvancedLayoutSupported()) {
-                mEndTouchArea.getBackground().setColorFilter(
-                        new PorterDuffColorFilter(mController.getColorItemBackground(),
-                                PorterDuff.Mode.SRC_IN));
+                mEndTouchArea.setBackgroundTintList(
+                        ColorStateList.valueOf(mController.getColorItemBackground()));
             }
             setUpContentDescriptionForView(mEndTouchArea, true, device);
         }
@@ -450,11 +451,11 @@
                 setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new));
                 final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
                 mTitleIcon.setImageDrawable(addDrawable);
-                mTitleIcon.setColorFilter(mController.getColorItemContent());
+                mTitleIcon.setImageTintList(
+                        ColorStateList.valueOf(mController.getColorItemContent()));
                 if (mController.isAdvancedLayoutSupported()) {
-                    mIconAreaLayout.getBackground().setColorFilter(
-                            new PorterDuffColorFilter(mController.getColorItemBackground(),
-                                    PorterDuff.Mode.SRC_IN));
+                    mIconAreaLayout.setBackgroundTintList(
+                            ColorStateList.valueOf(mController.getColorItemBackground()));
                 }
                 mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 2a2cf63..f76f049 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -23,8 +23,7 @@
 import android.annotation.DrawableRes;
 import android.app.WallpaperColors;
 import android.content.Context;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
+import android.content.res.ColorStateList;
 import android.graphics.Typeface;
 import android.graphics.drawable.ClipDrawable;
 import android.graphics.drawable.Drawable;
@@ -196,9 +195,8 @@
                 mIconAreaLayout.setOnClickListener(null);
                 mVolumeValueText.setTextColor(mController.getColorItemContent());
             }
-            mSeekBar.getProgressDrawable().setColorFilter(
-                    new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
-                            PorterDuff.Mode.SRC_IN));
+            mSeekBar.setProgressTintList(
+                    ColorStateList.valueOf(mController.getColorSeekbarProgress()));
         }
 
         abstract void onBind(int customizedItem);
@@ -224,16 +222,14 @@
                     updateSeekbarProgressBackground();
                 }
             }
-            mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
-                    isActive ? mController.getColorConnectedItemBackground()
-                            : mController.getColorItemBackground(),
-                    PorterDuff.Mode.SRC_IN));
+            mItemLayout.setBackgroundTintList(
+                    ColorStateList.valueOf(isActive ? mController.getColorConnectedItemBackground()
+                            : mController.getColorItemBackground()));
             if (mController.isAdvancedLayoutSupported()) {
-                mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
-                        showSeekBar ? mController.getColorSeekbarProgress()
+                mIconAreaLayout.setBackgroundTintList(
+                        ColorStateList.valueOf(showSeekBar ? mController.getColorSeekbarProgress()
                                 : showProgressBar ? mController.getColorConnectedItemBackground()
-                                        : mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
+                                        : mController.getColorItemBackground()));
             }
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
             mSeekBar.setAlpha(1);
@@ -251,7 +247,8 @@
                 params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
                         : mController.getItemMarginEndDefault();
             }
-            mTitleIcon.setColorFilter(mController.getColorItemContent());
+            mTitleIcon.setBackgroundTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
         }
 
         void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -274,15 +271,14 @@
                 backgroundDrawable = mContext.getDrawable(
                         showSeekBar ? R.drawable.media_output_item_background_active
                                 : R.drawable.media_output_item_background).mutate();
-                backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+                backgroundDrawable.setTint(
                         showSeekBar ? mController.getColorConnectedItemBackground()
-                                : mController.getColorItemBackground(), PorterDuff.Mode.SRC_IN));
-                mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
-                        showProgressBar || isFakeActive
+                                : mController.getColorItemBackground());
+                mIconAreaLayout.setBackgroundTintList(
+                        ColorStateList.valueOf(showProgressBar || isFakeActive
                                 ? mController.getColorConnectedItemBackground()
                                 : showSeekBar ? mController.getColorSeekbarProgress()
-                                        : mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
+                                        : mController.getColorItemBackground()));
                 if (showSeekBar) {
                     updateSeekbarProgressBackground();
                 }
@@ -297,9 +293,7 @@
                 backgroundDrawable = mContext.getDrawable(
                                 R.drawable.media_output_item_background)
                         .mutate();
-                backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
-                        mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
+                backgroundDrawable.setTint(mController.getColorItemBackground());
             }
             mItemLayout.setBackground(backgroundDrawable);
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
@@ -442,11 +436,10 @@
 
         void updateTitleIcon(@DrawableRes int id, int color) {
             mTitleIcon.setImageDrawable(mContext.getDrawable(id));
-            mTitleIcon.setColorFilter(color);
+            mTitleIcon.setImageTintList(ColorStateList.valueOf(color));
             if (mController.isAdvancedLayoutSupported()) {
-                mIconAreaLayout.getBackground().setColorFilter(
-                        new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
-                                PorterDuff.Mode.SRC_IN));
+                mIconAreaLayout.setBackgroundTintList(
+                        ColorStateList.valueOf(mController.getColorSeekbarProgress()));
             }
         }
 
@@ -462,9 +455,7 @@
             final Drawable backgroundDrawable = mContext.getDrawable(
                                     R.drawable.media_output_item_background_active)
                             .mutate();
-            backgroundDrawable.setColorFilter(
-                    new PorterDuffColorFilter(mController.getColorConnectedItemBackground(),
-                            PorterDuff.Mode.SRC_IN));
+            backgroundDrawable.setTint(mController.getColorConnectedItemBackground());
             mItemLayout.setBackground(backgroundDrawable);
         }
 
@@ -539,10 +530,8 @@
         Drawable getSpeakerDrawable() {
             final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
                     .mutate();
-            drawable.setColorFilter(
-                    new PorterDuffColorFilter(Utils.getColorStateListDefaultColor(mContext,
-                            R.color.media_dialog_item_main_content),
-                            PorterDuff.Mode.SRC_IN));
+            drawable.setTint(Utils.getColorStateListDefaultColor(mContext,
+                    R.color.media_dialog_item_main_content));
             return drawable;
         }
 
@@ -574,7 +563,9 @@
                         return;
                     }
                     mTitleIcon.setImageIcon(icon);
-                    mTitleIcon.setColorFilter(mController.getColorItemContent());
+                    icon.setTint(mController.getColorItemContent());
+                    mTitleIcon.setImageTintList(
+                            ColorStateList.valueOf(mController.getColorItemContent()));
                 });
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 2250d72..39d4e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -80,6 +80,10 @@
             Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingDeviceCount(deviceList);
 
         SysUiStatsLog.write(
@@ -105,6 +109,10 @@
             Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingMediaItemCount(deviceItemList);
 
         SysUiStatsLog.write(
@@ -176,6 +184,10 @@
             Log.e(TAG, "logRequestFailed - " + reason);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingDeviceCount(deviceList);
 
         SysUiStatsLog.write(
@@ -201,6 +213,10 @@
             Log.e(TAG, "logRequestFailed - " + reason);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingMediaItemCount(deviceItemList);
 
         SysUiStatsLog.write(
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
new file mode 100644
index 0000000..c48028c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.data.model
+
+import com.android.systemui.multishade.shared.model.ShadeId
+
+/** Models the current interaction with one of the shades. */
+data class MultiShadeInteractionModel(
+    /** The ID of the shade that the user is currently interacting with. */
+    val shadeId: ShadeId,
+    /** Whether the interaction is proxied (as in: coming from an external app or different UI). */
+    val isProxied: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
new file mode 100644
index 0000000..86f0c0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.data.remoteproxy
+
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+/**
+ * Acts as a hub for routing proxied user input into the multi shade system.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself. This class
+ * is that proxy.
+ */
+@Singleton
+class MultiShadeInputProxy @Inject constructor() {
+    private val _proxiedTouch =
+        MutableSharedFlow<ProxiedInputModel>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
+    val proxiedInput: Flow<ProxiedInputModel> = _proxiedTouch.asSharedFlow()
+
+    fun onProxiedInput(proxiedInput: ProxiedInputModel) {
+        _proxiedTouch.tryEmit(proxiedInput)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
new file mode 100644
index 0000000..1172030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.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.systemui.multishade.data.repository
+
+import android.content.Context
+import androidx.annotation.FloatRange
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Encapsulates application state for all shades. */
+@SysUISingleton
+class MultiShadeRepository
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    inputProxy: MultiShadeInputProxy,
+) {
+    /**
+     * Remote input coming from sources outside of system UI (for example, swiping down on the
+     * Launcher or from the status bar).
+     */
+    val proxiedInput: Flow<ProxiedInputModel> = inputProxy.proxiedInput
+
+    /** Width of the left-hand side shade, in pixels. */
+    private val leftShadeWidthPx =
+        applicationContext.resources.getDimensionPixelSize(R.dimen.left_shade_width)
+
+    /** Width of the right-hand side shade, in pixels. */
+    private val rightShadeWidthPx =
+        applicationContext.resources.getDimensionPixelSize(R.dimen.right_shade_width)
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val swipeCollapseThreshold =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_collapse_threshold))
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val swipeExpandThreshold =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_expand_threshold))
+
+    /**
+     * Maximum opacity when the scrim that shows up behind the dual shades is fully visible.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val dualShadeScrimAlpha =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.dual_shade_scrim_alpha))
+
+    /** The current configuration of the shade system. */
+    val shadeConfig: StateFlow<ShadeConfig> =
+        MutableStateFlow(
+                if (applicationContext.resources.getBoolean(R.bool.dual_shade_enabled)) {
+                    ShadeConfig.DualShadeConfig(
+                        leftShadeWidthPx = leftShadeWidthPx,
+                        rightShadeWidthPx = rightShadeWidthPx,
+                        swipeCollapseThreshold = swipeCollapseThreshold,
+                        swipeExpandThreshold = swipeExpandThreshold,
+                        splitFraction =
+                            applicationContext.resources.getFloat(
+                                R.dimen.dual_shade_split_fraction
+                            ),
+                        scrimAlpha = dualShadeScrimAlpha,
+                    )
+                } else {
+                    ShadeConfig.SingleShadeConfig(
+                        swipeCollapseThreshold = swipeCollapseThreshold,
+                        swipeExpandThreshold = swipeExpandThreshold,
+                    )
+                }
+            )
+            .asStateFlow()
+
+    private val _forceCollapseAll = MutableStateFlow(false)
+    /** Whether all shades should be collapsed. */
+    val forceCollapseAll: StateFlow<Boolean> = _forceCollapseAll.asStateFlow()
+
+    private val _shadeInteraction = MutableStateFlow<MultiShadeInteractionModel?>(null)
+    /** The current shade interaction or `null` if no shade is interacted with currently. */
+    val shadeInteraction: StateFlow<MultiShadeInteractionModel?> = _shadeInteraction.asStateFlow()
+
+    private val stateByShade = mutableMapOf<ShadeId, MutableStateFlow<ShadeModel>>()
+
+    /** The model for the shade with the given ID. */
+    fun getShade(
+        shadeId: ShadeId,
+    ): StateFlow<ShadeModel> {
+        return getMutableShade(shadeId).asStateFlow()
+    }
+
+    /** Sets the expansion amount for the shade with the given ID. */
+    fun setExpansion(
+        shadeId: ShadeId,
+        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+    ) {
+        getMutableShade(shadeId).let { mutableState ->
+            mutableState.value = mutableState.value.copy(expansion = expansion)
+        }
+    }
+
+    /** Sets whether all shades should be immediately forced to collapse. */
+    fun setForceCollapseAll(isForced: Boolean) {
+        _forceCollapseAll.value = isForced
+    }
+
+    /** Sets the current shade interaction; use `null` if no shade is interacted with currently. */
+    fun setShadeInteraction(shadeInteraction: MultiShadeInteractionModel?) {
+        _shadeInteraction.value = shadeInteraction
+    }
+
+    private fun getMutableShade(id: ShadeId): MutableStateFlow<ShadeModel> {
+        return stateByShade.getOrPut(id) { MutableStateFlow(ShadeModel(id)) }
+    }
+
+    /** Asserts that the given [Float] is in the range of `0` and `1`, inclusive. */
+    private fun checkInBounds(float: Float): Float {
+        check(float in 0f..1f) { "$float isn't between 0 and 1." }
+        return float
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
new file mode 100644
index 0000000..b9f6d83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.domain.interactor
+
+import androidx.annotation.FloatRange
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.yield
+
+/** Encapsulates business logic related to interactions with the multi-shade system. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MultiShadeInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val repository: MultiShadeRepository,
+    private val inputProxy: MultiShadeInputProxy,
+) {
+    /** The current configuration of the shade system. */
+    val shadeConfig: StateFlow<ShadeConfig> = repository.shadeConfig
+
+    /** The expansion of the shade that's most expanded. */
+    val maxShadeExpansion: Flow<Float> =
+        repository.shadeConfig.flatMapLatest { shadeConfig ->
+            combine(allShades(shadeConfig)) { shadeModels ->
+                shadeModels.maxOfOrNull { it.expansion } ?: 0f
+            }
+        }
+
+    /**
+     * A _processed_ version of the proxied input flow.
+     *
+     * All internal dependencies on the proxied input flow *must* use this one for two reasons:
+     * 1. It's a [SharedFlow] so we only do the upstream work once, no matter how many usages we
+     *    actually have.
+     * 2. It actually does some preprocessing as the proxied input events stream through, handling
+     *    common things like recording the current state of the system based on incoming input
+     *    events.
+     */
+    private val processedProxiedInput: SharedFlow<ProxiedInputModel> =
+        combine(
+                repository.shadeConfig,
+                repository.proxiedInput.distinctUntilChanged(),
+                ::Pair,
+            )
+            .map { (shadeConfig, proxiedInput) ->
+                if (proxiedInput !is ProxiedInputModel.OnTap) {
+                    // If the user is interacting with any other gesture type (for instance,
+                    // dragging),
+                    // we no longer want to force collapse all shades.
+                    repository.setForceCollapseAll(false)
+                }
+
+                when (proxiedInput) {
+                    is ProxiedInputModel.OnDrag -> {
+                        val affectedShadeId = affectedShadeId(shadeConfig, proxiedInput.xFraction)
+                        // This might be the start of a new drag gesture, let's update our
+                        // application
+                        // state to record that fact.
+                        onUserInteractionStarted(
+                            shadeId = affectedShadeId,
+                            isProxied = true,
+                        )
+                    }
+                    is ProxiedInputModel.OnTap -> {
+                        // Tapping outside any shade collapses all shades. This code path is not hit
+                        // for
+                        // taps that happen _inside_ a shade as that input event is directly applied
+                        // through the UI and is, hence, not a proxied input.
+                        collapseAll()
+                    }
+                    else -> Unit
+                }
+
+                proxiedInput
+            }
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                replay = 1,
+            )
+
+    /** Whether the shade with the given ID should be visible. */
+    fun isVisible(shadeId: ShadeId): Flow<Boolean> {
+        return repository.shadeConfig.map { shadeConfig -> shadeConfig.shadeIds.contains(shadeId) }
+    }
+
+    /** Whether direct user input is allowed on the shade with the given ID. */
+    fun isNonProxiedInputAllowed(shadeId: ShadeId): Flow<Boolean> {
+        return combine(
+                isForceCollapsed(shadeId),
+                repository.shadeInteraction,
+                ::Pair,
+            )
+            .map { (isForceCollapsed, shadeInteraction) ->
+                !isForceCollapsed && shadeInteraction?.isProxied != true
+            }
+    }
+
+    /** Whether the shade with the given ID is forced to collapse. */
+    fun isForceCollapsed(shadeId: ShadeId): Flow<Boolean> {
+        return combine(
+                repository.forceCollapseAll,
+                repository.shadeInteraction.map { it?.shadeId },
+                ::Pair,
+            )
+            .map { (collapseAll, userInteractedShadeIdOrNull) ->
+                val counterpartShadeIdOrNull =
+                    when (shadeId) {
+                        ShadeId.SINGLE -> null
+                        ShadeId.LEFT -> ShadeId.RIGHT
+                        ShadeId.RIGHT -> ShadeId.LEFT
+                    }
+
+                when {
+                    // If all shades have been told to collapse (by a tap outside, for example),
+                    // then this shade is collapsed.
+                    collapseAll -> true
+                    // A shade that doesn't have a counterpart shade cannot be force-collapsed by
+                    // interactions on the counterpart shade.
+                    counterpartShadeIdOrNull == null -> false
+                    // If the current user interaction is on the counterpart shade, then this shade
+                    // should be force-collapsed.
+                    else -> userInteractedShadeIdOrNull == counterpartShadeIdOrNull
+                }
+            }
+    }
+
+    /**
+     * Proxied input affecting the shade with the given ID. This is input coming from sources
+     * outside of system UI (for example, swiping down on the Launcher or from the status bar) or
+     * outside the UI of any shade (for example, the scrim that's shown behind the shades).
+     */
+    fun proxiedInput(shadeId: ShadeId): Flow<ProxiedInputModel?> {
+        return combine(
+                processedProxiedInput,
+                isForceCollapsed(shadeId).distinctUntilChanged(),
+                repository.shadeInteraction,
+                ::Triple,
+            )
+            .map { (proxiedInput, isForceCollapsed, shadeInteraction) ->
+                when {
+                    // If the shade is force-collapsed, we ignored proxied input on it.
+                    isForceCollapsed -> null
+                    // If the proxied input does not belong to this shade, ignore it.
+                    shadeInteraction?.shadeId != shadeId -> null
+                    // If there is ongoing non-proxied user input on any shade, ignore the
+                    // proxied input.
+                    !shadeInteraction.isProxied -> null
+                    // Otherwise, send the proxied input downstream.
+                    else -> proxiedInput
+                }
+            }
+            .onEach { proxiedInput ->
+                // We use yield() to make sure that the following block of code happens _after_
+                // downstream collectors had a chance to process the proxied input. Otherwise, we
+                // might change our state to clear the current UserInteraction _before_ those
+                // downstream collectors get a chance to process the proxied input, which will make
+                // them ignore it (since they ignore proxied input when the current user interaction
+                // doesn't match their shade).
+                yield()
+
+                if (
+                    proxiedInput is ProxiedInputModel.OnDragEnd ||
+                        proxiedInput is ProxiedInputModel.OnDragCancel
+                ) {
+                    onUserInteractionEnded(shadeId = shadeId, isProxied = true)
+                }
+            }
+    }
+
+    /** Sets the expansion amount for the shade with the given ID. */
+    fun setExpansion(
+        shadeId: ShadeId,
+        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+    ) {
+        repository.setExpansion(shadeId, expansion)
+    }
+
+    /** Collapses all shades. */
+    fun collapseAll() {
+        repository.setForceCollapseAll(true)
+    }
+
+    /**
+     * Notifies that a new non-proxied interaction may have started. Safe to call multiple times for
+     * the same interaction as it won't overwrite an existing interaction.
+     *
+     * Existing interactions can be cleared by calling [onUserInteractionEnded].
+     */
+    fun onUserInteractionStarted(shadeId: ShadeId) {
+        onUserInteractionStarted(
+            shadeId = shadeId,
+            isProxied = false,
+        )
+    }
+
+    /**
+     * Notifies that the current non-proxied interaction has ended.
+     *
+     * Safe to call multiple times, even if there's no current interaction or even if the current
+     * interaction doesn't belong to the given shade or is proxied as the code is a no-op unless
+     * there's a match between the parameters and the current interaction.
+     */
+    fun onUserInteractionEnded(
+        shadeId: ShadeId,
+    ) {
+        onUserInteractionEnded(
+            shadeId = shadeId,
+            isProxied = false,
+        )
+    }
+
+    fun sendProxiedInput(proxiedInput: ProxiedInputModel) {
+        inputProxy.onProxiedInput(proxiedInput)
+    }
+
+    /**
+     * Notifies that a new interaction may have started. Safe to call multiple times for the same
+     * interaction as it won't overwrite an existing interaction.
+     *
+     * Existing interactions can be cleared by calling [onUserInteractionEnded].
+     */
+    private fun onUserInteractionStarted(
+        shadeId: ShadeId,
+        isProxied: Boolean,
+    ) {
+        if (repository.shadeInteraction.value != null) {
+            return
+        }
+
+        repository.setShadeInteraction(
+            MultiShadeInteractionModel(
+                shadeId = shadeId,
+                isProxied = isProxied,
+            )
+        )
+    }
+
+    /**
+     * Notifies that the current interaction has ended.
+     *
+     * Safe to call multiple times, even if there's no current interaction or even if the current
+     * interaction doesn't belong to the given shade or [isProxied] value as the code is a no-op
+     * unless there's a match between the parameters and the current interaction.
+     */
+    private fun onUserInteractionEnded(
+        shadeId: ShadeId,
+        isProxied: Boolean,
+    ) {
+        repository.shadeInteraction.value?.let { (interactionShadeId, isInteractionProxied) ->
+            if (shadeId == interactionShadeId && isProxied == isInteractionProxied) {
+                repository.setShadeInteraction(null)
+            }
+        }
+    }
+
+    /**
+     * Returns the ID of the shade that's affected by user input at a given coordinate.
+     *
+     * @param config The shade configuration being used.
+     * @param xFraction The horizontal position of the user input as a fraction along the width of
+     *   its container where `0` is all the way to the left and `1` is all the way to the right.
+     */
+    private fun affectedShadeId(
+        config: ShadeConfig,
+        @FloatRange(from = 0.0, to = 1.0) xFraction: Float,
+    ): ShadeId {
+        return if (config is ShadeConfig.DualShadeConfig) {
+            if (xFraction <= config.splitFraction) {
+                ShadeId.LEFT
+            } else {
+                ShadeId.RIGHT
+            }
+        } else {
+            ShadeId.SINGLE
+        }
+    }
+
+    /** Returns the list of flows of all the shades in the given configuration. */
+    private fun allShades(
+        config: ShadeConfig,
+    ): List<Flow<ShadeModel>> {
+        return config.shadeIds.map { shadeId -> repository.getShade(shadeId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
new file mode 100644
index 0000000..ee1dd65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/**
+ * Models a part of an ongoing proxied user input gesture.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself.
+ */
+sealed class ProxiedInputModel {
+    /** The user is dragging their pointer. */
+    data class OnDrag(
+        /**
+         * The relative position of the pointer as a fraction of its container width where `0` is
+         * all the way to the left and `1` is all the way to the right.
+         */
+        @FloatRange(from = 0.0, to = 1.0) val xFraction: Float,
+        /** The amount that the pointer was dragged, in pixels. */
+        val yDragAmountPx: Float,
+    ) : ProxiedInputModel()
+
+    /** The user finished dragging by lifting up their pointer. */
+    object OnDragEnd : ProxiedInputModel()
+
+    /**
+     * The drag gesture has been canceled. Usually because the pointer exited the draggable area.
+     */
+    object OnDragCancel : ProxiedInputModel()
+
+    /** The user has tapped (clicked). */
+    object OnTap : ProxiedInputModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
new file mode 100644
index 0000000..a4cd35c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/** Enumerates the various possible configurations of the shade system. */
+sealed class ShadeConfig(
+
+    /** IDs of the shade(s) in this configuration. */
+    open val shadeIds: List<ShadeId>,
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     */
+    @FloatRange(from = 0.0, to = 1.0) open val swipeCollapseThreshold: Float,
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     */
+    @FloatRange(from = 0.0, to = 1.0) open val swipeExpandThreshold: Float,
+) {
+
+    /** There is a single shade. */
+    data class SingleShadeConfig(
+        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+    ) :
+        ShadeConfig(
+            shadeIds = listOf(ShadeId.SINGLE),
+            swipeCollapseThreshold = swipeCollapseThreshold,
+            swipeExpandThreshold = swipeExpandThreshold,
+        )
+
+    /** There are two shades arranged side-by-side. */
+    data class DualShadeConfig(
+        /** Width of the left-hand side shade. */
+        val leftShadeWidthPx: Int,
+        /** Width of the right-hand side shade. */
+        val rightShadeWidthPx: Int,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+        /**
+         * The position of the "split" between interaction areas for each of the shades, as a
+         * fraction of the width of the container.
+         *
+         * Interactions that occur on the start-side (left-hand side in left-to-right languages like
+         * English) affect the start-side shade. Interactions that occur on the end-side (right-hand
+         * side in left-to-right languages like English) affect the end-side shade.
+         */
+        @FloatRange(from = 0.0, to = 1.0) val splitFraction: Float,
+        /** Maximum opacity when the scrim that shows up behind the dual shades is fully visible. */
+        @FloatRange(from = 0.0, to = 1.0) val scrimAlpha: Float,
+    ) :
+        ShadeConfig(
+            shadeIds = listOf(ShadeId.LEFT, ShadeId.RIGHT),
+            swipeCollapseThreshold = swipeCollapseThreshold,
+            swipeExpandThreshold = swipeExpandThreshold,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
new file mode 100644
index 0000000..9e02657
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.shared.model
+
+/** Enumerates all known shade IDs. */
+enum class ShadeId {
+    /** ID of the shade on the left in dual shade configurations. */
+    LEFT,
+    /** ID of the shade on the right in dual shade configurations. */
+    RIGHT,
+    /** ID of the single shade in single shade configurations. */
+    SINGLE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
new file mode 100644
index 0000000..49ac64c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/** Models the current state of a shade. */
+data class ShadeModel(
+    val id: ShadeId,
+    @FloatRange(from = 0.0, to = 1.0) val expansion: Float = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
new file mode 100644
index 0000000..ce6ab97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for UI that supports multi (or single) shade. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class MultiShadeViewModel(
+    viewModelScope: CoroutineScope,
+    private val interactor: MultiShadeInteractor,
+) {
+    /** Models UI state for the single shade. */
+    val singleShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.SINGLE,
+            interactor,
+        )
+
+    /** Models UI state for the shade on the left-hand side. */
+    val leftShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.LEFT,
+            interactor,
+        )
+
+    /** Models UI state for the shade on the right-hand side. */
+    val rightShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.RIGHT,
+            interactor,
+        )
+
+    /** The amount of alpha that the scrim should have. This is a value between `0` and `1`. */
+    val scrimAlpha: StateFlow<Float> =
+        combine(
+                interactor.maxShadeExpansion,
+                interactor.shadeConfig
+                    .map { it as? ShadeConfig.DualShadeConfig }
+                    .map { dualShadeConfigOrNull -> dualShadeConfigOrNull?.scrimAlpha ?: 0f },
+                ::Pair,
+            )
+            .map { (anyShadeExpansion, scrimAlpha) ->
+                (anyShadeExpansion * scrimAlpha).coerceIn(0f, 1f)
+            }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = 0f,
+            )
+
+    /** Whether the scrim should accept touch events. */
+    val isScrimEnabled: StateFlow<Boolean> =
+        interactor.shadeConfig
+            .flatMapLatest { shadeConfig ->
+                when (shadeConfig) {
+                    // In the dual shade configuration, the scrim is enabled when the expansion is
+                    // greater than zero on any one of the shades.
+                    is ShadeConfig.DualShadeConfig ->
+                        interactor.maxShadeExpansion
+                            .map { expansion -> expansion > 0 }
+                            .distinctUntilChanged()
+                    // No scrim in the single shade configuration.
+                    is ShadeConfig.SingleShadeConfig -> flowOf(false)
+                }
+            }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Notifies that the scrim has been touched. */
+    fun onScrimTouched(proxiedInput: ProxiedInputModel) {
+        if (!isScrimEnabled.value) {
+            return
+        }
+
+        interactor.sendProxiedInput(proxiedInput)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
new file mode 100644
index 0000000..e828dbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import androidx.annotation.FloatRange
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for a single shade. */
+class ShadeViewModel(
+    viewModelScope: CoroutineScope,
+    private val shadeId: ShadeId,
+    private val interactor: MultiShadeInteractor,
+) {
+    /** Whether the shade is visible. */
+    val isVisible: StateFlow<Boolean> =
+        interactor
+            .isVisible(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether swiping on the shade UI is currently enabled. */
+    val isSwipingEnabled: StateFlow<Boolean> =
+        interactor
+            .isNonProxiedInputAllowed(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether the shade must be collapsed immediately. */
+    val isForceCollapsed: Flow<Boolean> =
+        interactor.isForceCollapsed(shadeId).distinctUntilChanged()
+
+    /** The width of the shade. */
+    val width: StateFlow<Size> =
+        interactor.shadeConfig
+            .map { shadeWidth(it) }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = shadeWidth(interactor.shadeConfig.value),
+            )
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     */
+    val swipeCollapseThreshold: StateFlow<Float> =
+        interactor.shadeConfig
+            .map { it.swipeCollapseThreshold }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.shadeConfig.value.swipeCollapseThreshold,
+            )
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     */
+    val swipeExpandThreshold: StateFlow<Float> =
+        interactor.shadeConfig
+            .map { it.swipeExpandThreshold }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.shadeConfig.value.swipeExpandThreshold,
+            )
+
+    /**
+     * Proxied input affecting the shade. This is input coming from sources outside of system UI
+     * (for example, swiping down on the Launcher or from the status bar) or outside the UI of any
+     * shade (for example, the scrim that's shown behind the shades).
+     */
+    val proxiedInput: Flow<ProxiedInputModel?> =
+        interactor
+            .proxiedInput(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
+    /** Notifies that the expansion amount for the shade has changed. */
+    fun onExpansionChanged(
+        expansion: Float,
+    ) {
+        interactor.setExpansion(shadeId, expansion.coerceIn(0f, 1f))
+    }
+
+    /** Notifies that a drag gesture has started. */
+    fun onDragStarted() {
+        interactor.onUserInteractionStarted(shadeId)
+    }
+
+    /** Notifies that a drag gesture has ended. */
+    fun onDragEnded() {
+        interactor.onUserInteractionEnded(shadeId = shadeId)
+    }
+
+    private fun shadeWidth(shadeConfig: ShadeConfig): Size {
+        return when (shadeId) {
+            ShadeId.LEFT ->
+                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.leftShadeWidthPx ?: 0)
+            ShadeId.RIGHT ->
+                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.rightShadeWidthPx ?: 0)
+            ShadeId.SINGLE -> Size.Fraction(1f)
+        }
+    }
+
+    sealed class Size {
+        data class Fraction(
+            @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
+        ) : Size()
+        data class Pixels(
+            val pixels: Int,
+        ) : Size()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index ca8e101..02a60ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -481,7 +481,6 @@
             mCropView.setExtraPadding(extraPadding + mPreview.getPaddingTop(),
                     extraPadding + mPreview.getPaddingBottom());
             imageTop += (previewHeight - imageHeight) / 2;
-            mCropView.setExtraPadding(extraPadding, extraPadding);
             mCropView.setImageWidth(previewWidth);
             scale = previewWidth / (float) mPreview.getDrawable().getIntrinsicWidth();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 587c65d..06426b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3581,12 +3581,15 @@
     }
 
     private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+        // don't fling while in keyguard to avoid jump in shade expand animation
+        boolean fullyExpandedInKeyguard = mBarState == KEYGUARD && mExpandedFraction >= 1.0;
         mTrackingPointer = -1;
         mAmbientState.setSwipingUp(false);
-        if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
+        if (!fullyExpandedInKeyguard && ((mTracking && mTouchSlopExceeded)
+                || Math.abs(x - mInitialExpandX) > mTouchSlop
                 || Math.abs(y - mInitialExpandY) > mTouchSlop
                 || (!isFullyExpanded() && !isFullyCollapsed())
-                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel)) {
             mVelocityTracker.computeCurrentVelocity(1000);
             float vel = mVelocityTracker.getYVelocity();
             float vectorVel = (float) Math.hypot(
@@ -3634,9 +3637,9 @@
             if (mUpdateFlingOnLayout) {
                 mUpdateFlingVelocity = vel;
             }
-        } else if (!mCentralSurfaces.isBouncerShowing()
+        } else if (fullyExpandedInKeyguard || (!mCentralSurfaces.isBouncerShowing()
                 && !mAlternateBouncerInteractor.isVisibleState()
-                && !mKeyguardStateController.isKeyguardGoingAway()) {
+                && !mKeyguardStateController.isKeyguardGoingAway())) {
             onEmptySpaceClick();
             onTrackingStopped(true);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index e2f31e8..2899081 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade;
 
+import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_BACK;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.app.StatusBarManager;
@@ -39,6 +40,7 @@
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -88,10 +90,12 @@
     private final NotificationInsetsController mNotificationInsetsController;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+    private final boolean mIsTrackpadGestureBackEnabled;
     private GestureDetector mPulsingWakeupGestureHandler;
     private View mBrightnessMirror;
     private boolean mTouchActive;
     private boolean mTouchCancelled;
+    private MotionEvent mDownEvent;
     private boolean mExpandAnimationRunning;
     private NotificationStackScrollLayout mStackScrollLayout;
     private PhoneStatusBarViewController mStatusBarViewController;
@@ -137,8 +141,8 @@
             AlternateBouncerInteractor alternateBouncerInteractor,
             UdfpsOverlayInteractor udfpsOverlayInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel
-    ) {
+            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            FeatureFlags featureFlags) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -159,6 +163,7 @@
         mNotificationInsetsController = notificationInsetsController;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
+        mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_BACK);
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -219,9 +224,11 @@
                 if (isDown) {
                     mTouchActive = true;
                     mTouchCancelled = false;
+                    mDownEvent = ev;
                 } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
                         || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
                     mTouchActive = false;
+                    mDownEvent = null;
                 }
                 if (mTouchCancelled || mExpandAnimationRunning) {
                     return false;
@@ -447,9 +454,17 @@
     public void cancelCurrentTouch() {
         if (mTouchActive) {
             final long now = SystemClock.uptimeMillis();
-            MotionEvent event = MotionEvent.obtain(now, now,
-                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            final MotionEvent event;
+            if (mIsTrackpadGestureBackEnabled) {
+                event = MotionEvent.obtain(mDownEvent);
+                event.setDownTime(now);
+                event.setAction(MotionEvent.ACTION_CANCEL);
+                event.setLocation(0.0f, 0.0f);
+            } else {
+                event = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            }
             mView.dispatchTouchEvent(event);
             event.recycle();
             mTouchCancelled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index 5736a5c..26149321 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -37,7 +37,8 @@
         fun create(
             @BindsInstance parent: ViewGroup,
             @BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
-            @BindsInstance onAttachListener: View.OnAttachStateChangeListener
+            @BindsInstance onAttachListener: View.OnAttachStateChangeListener,
+            @BindsInstance viewWithCustomLayout: View? = null
         ): SmartspaceViewComponent
     }
 
@@ -53,10 +54,13 @@
             falsingManager: FalsingManager,
             parent: ViewGroup,
             @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
+            viewWithCustomLayout: View?,
             onAttachListener: View.OnAttachStateChangeListener
         ):
                 BcSmartspaceDataPlugin.SmartspaceView {
-            val ssView = plugin.getView(parent)
+            val ssView = viewWithCustomLayout
+                    as? BcSmartspaceDataPlugin.SmartspaceView
+                    ?: plugin.getView(parent)
             // Currently, this is only used to provide SmartspaceView on Dream surface.
             ssView.setUiSurface(UI_SURFACE_DREAM)
             ssView.registerDataProvider(plugin)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 1004ec1..4d0e746 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -389,11 +389,11 @@
             // First check whether this notification should launch a full screen intent, and
             // launch it if needed.
             val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
-            if (fsiDecision != null && fsiDecision.shouldLaunch) {
-                mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
+            mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
+            if (fsiDecision.shouldLaunch) {
                 mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
             } else if (mFlags.fsiOnDNDUpdate() &&
-                fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) {
+                fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
                 // If DND was the only reason this entry was suppressed, note it for potential
                 // reconsideration on later ranking updates.
                 addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
@@ -514,14 +514,24 @@
                         mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
                     if (decision.shouldLaunch) {
                         // Log both the launch of the full screen and also that this was via a
-                        // ranking update.
-                        mLogger.logEntryUpdatedToFullScreen(entry.key)
+                        // ranking update, and finally revoke candidacy for FSI reconsideration
+                        mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name)
                         mNotificationInterruptStateProvider.logFullScreenIntentDecision(
                             entry, decision)
                         mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+                        mFSIUpdateCandidates.remove(entry.key)
 
                         // if we launch the FSI then this is no longer a candidate for HUN
                         continue
+                    } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
+                        // decision has not changed; no need to log
+                    } else {
+                        // some other condition is now blocking FSI; log that and revoke candidacy
+                        // for FSI reconsideration
+                        mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name)
+                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
+                            entry, decision)
+                        mFSIUpdateCandidates.remove(entry.key)
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 2c6bf6b..e936559 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -70,11 +70,21 @@
         })
     }
 
-    fun logEntryUpdatedToFullScreen(key: String) {
+    fun logEntryUpdatedToFullScreen(key: String, reason: String) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
+            str2 = reason
         }, {
-            "updating entry to launch full screen intent: $str1"
+            "updating entry to launch full screen intent: $str1 because $str2"
+        })
+    }
+
+    fun logEntryDisqualifiedFromFullScreen(key: String, reason: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+            str2 = reason
+        }, {
+            "updated entry no longer qualifies for full screen intent: $str1 because $str2"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 9001470..5ba8801 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.interruption;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
@@ -153,7 +155,8 @@
      * @param entry the entry to evaluate
      * @return FullScreenIntentDecision representing the decision for whether to show the intent
      */
-    FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry);
+    @NonNull
+    FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry);
 
     /**
      * Write the full screen launch decision for the given entry to logs.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 9f45b9d..274377f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -35,6 +35,8 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -232,6 +234,7 @@
     // suppressor.
     //
     // If the entry was not suppressed by DND, just returns the given decision.
+    @NonNull
     private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision,
             boolean suppressedByDND) {
         if (suppressedByDND) {
@@ -243,7 +246,7 @@
     }
 
     @Override
-    public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
+    public FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry) {
         if (entry.getSbn().getNotification().fullScreenIntent == null) {
             if (entry.isStickyAndNotDemoted()) {
                 return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN;
@@ -336,52 +339,30 @@
         final int uid = entry.getSbn().getUid();
         final String packageName = entry.getSbn().getPackageName();
         switch (decision) {
-            case NO_FSI_SHOW_STICKY_HUN:
-                mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN");
-                return;
             case NO_FULL_SCREEN_INTENT:
-                return;
-            case NO_FSI_SUPPRESSED_BY_DND:
-            case NO_FSI_SUPPRESSED_ONLY_BY_DND:
-                mLogger.logNoFullscreen(entry, "Suppressed by DND");
-                return;
-            case NO_FSI_NOT_IMPORTANT_ENOUGH:
-                mLogger.logNoFullscreen(entry, "Not important enough");
+                // explicitly prevent logging for this (frequent) case
                 return;
             case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR:
                 android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
                         "groupAlertBehavior");
                 mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid,
                         packageName);
-                mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
-                return;
-            case FSI_DEVICE_NOT_INTERACTIVE:
-                mLogger.logFullscreen(entry, "Device is not interactive");
-                return;
-            case FSI_DEVICE_IS_DREAMING:
-                mLogger.logFullscreen(entry, "Device is dreaming");
-                return;
-            case FSI_KEYGUARD_SHOWING:
-                mLogger.logFullscreen(entry, "Keyguard is showing");
-                return;
-            case NO_FSI_EXPECTED_TO_HUN:
-                mLogger.logNoFullscreen(entry, "Expected to HUN");
-                return;
-            case FSI_KEYGUARD_OCCLUDED:
-                mLogger.logFullscreen(entry,
-                        "Expected not to HUN while keyguard occluded");
-                return;
-            case FSI_LOCKED_SHADE:
-                mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+                mLogger.logNoFullscreenWarning(entry,
+                        decision + ": GroupAlertBehavior will prevent HUN");
                 return;
             case NO_FSI_NO_HUN_OR_KEYGUARD:
                 android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
                         "no hun or keyguard");
                 mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
-                mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+                mLogger.logNoFullscreenWarning(entry,
+                        decision + ": Expected not to HUN while not on keyguard");
                 return;
-            case FSI_EXPECTED_NOT_TO_HUN:
-                mLogger.logFullscreen(entry, "Expected not to HUN");
+            default:
+                if (decision.shouldLaunch) {
+                    mLogger.logFullscreen(entry, decision.name());
+                } else {
+                    mLogger.logNoFullscreen(entry, decision.name());
+                }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a5b7e94..33cbf06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -184,7 +184,6 @@
     private int mMaxSmallHeight;
     private int mMaxSmallHeightLarge;
     private int mMaxExpandedHeight;
-    private int mIncreasedPaddingBetweenElements;
     private int mNotificationLaunchHeight;
     private boolean mMustStayOnScreen;
 
@@ -3065,14 +3064,6 @@
         return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
     }
 
-    @Override
-    public int getExtraBottomPadding() {
-        if (mIsSummaryWithChildren && isGroupExpanded()) {
-            return mIncreasedPaddingBetweenElements;
-        }
-        return 0;
-    }
-
     public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
         mIsInlineReplyAnimationFlagEnabled = isEnabled;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 25c7264..9df6ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -451,7 +451,7 @@
     protected void updateClipping() {
         if (mClipToActualHeight && shouldClipToActualHeight()) {
             int top = getClipTopAmount();
-            int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
+            int bottom = Math.max(Math.max(getActualHeight()
                     - mClipBottomAmount, top), mMinimumHeightForClipping);
             mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
             setClipBounds(mClipRect);
@@ -592,13 +592,6 @@
     }
 
     /**
-     * @return padding used to alter how much of the view is clipped.
-     */
-    public int getExtraBottomPadding() {
-        return 0;
-    }
-
-    /**
      * @return true if the group's expansion state is changing, false otherwise.
      */
     public boolean isGroupExpansionChanging() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e2e2a23..2c088fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4499,7 +4499,7 @@
                 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
             } else {
                 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
-                        expandableView.getTranslationY() - previous.getExtraBottomPadding();
+                        expandableView.getTranslationY();
                 expandableView.setFakeShadowIntensity(
                         diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
                         previous.getOutlineAlpha(), (int) yLocation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f1fc386..f866d65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.IntentFilter
-import android.telephony.CellSignalStrength
 import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
@@ -28,12 +27,12 @@
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.ERI_OFF
+import android.telephony.TelephonyManager.ERI_FLASH
+import android.telephony.TelephonyManager.ERI_ON
 import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import com.android.settingslib.Utils
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.table.TableLogBuffer
@@ -59,16 +58,14 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.scan
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -100,8 +97,6 @@
         }
     }
 
-    private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-
     /**
      * This flow defines the single shared connection to system_server via TelephonyCallback. Any
      * new callback should be added to this listener and funneled through callbackEvents via a data
@@ -109,9 +104,15 @@
      *
      * The reason we need to do this is because TelephonyManager limits the number of registered
      * listeners per-process, so we don't want to create a new listener for every callback.
+     *
+     * A note on the design for back pressure here: We use the [coalesce] operator here to change
+     * the backpressure strategy to store exactly the last callback event of _each type_ here, as
+     * opposed to the default strategy which is to drop the oldest event (regardless of type). This
+     * means that we should never miss any single event as long as the flow has been started.
      */
-    private val callbackEvents: SharedFlow<CallbackEvent> =
-        conflatedCallbackFlow {
+    private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
+        val initial = TelephonyCallbackState()
+        callbackFlow {
                 val callback =
                     object :
                         TelephonyCallback(),
@@ -165,48 +166,50 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
-            .shareIn(scope, SharingStarted.WhileSubscribed())
+            .scan(initial = initial) { state, event -> state.applyEvent(event) }
+            .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initial)
+    }
 
     override val isEmergencyOnly =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { it.serviceState.isEmergencyOnly }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isRoaming =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { it.serviceState.roaming }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val operatorAlphaShort =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { it.serviceState.operatorAlphaShort }
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     override val isInService =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { Utils.isInService(it.serviceState) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isGsm =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .mapNotNull { it.onSignalStrengthChanged }
             .map { it.signalStrength.isGsm }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val cdmaLevel =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .mapNotNull { it.onSignalStrengthChanged }
             .map {
                 it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
                     strengths ->
                     if (strengths.isNotEmpty()) {
                         strengths[0].level
                     } else {
-                        CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+                        SIGNAL_STRENGTH_NONE_OR_UNKNOWN
                     }
                 }
             }
@@ -214,19 +217,19 @@
 
     override val primaryLevel =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .mapNotNull { it.onSignalStrengthChanged }
             .map { it.signalStrength.level }
             .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
 
     override val dataConnectionState =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>()
+            .mapNotNull { it.onDataConnectionStateChanged }
             .map { it.dataState.toDataConnectionType() }
             .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
 
     override val dataActivityDirection =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnDataActivity>()
+            .mapNotNull { it.onDataActivity }
             .map { it.direction.toMobileDataActivityModel() }
             .stateIn(
                 scope,
@@ -236,28 +239,26 @@
 
     override val carrierNetworkChangeActive =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnCarrierNetworkChange>()
+            .mapNotNull { it.onCarrierNetworkChange }
             .map { it.active }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val resolvedNetworkType =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnDisplayInfoChanged>()
+            .mapNotNull { it.onDisplayInfoChanged }
             .map {
-                if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
-                    UnknownNetworkType
-                } else if (
-                    it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
-                ) {
-                    DefaultNetworkType(
-                        mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
-                    )
-                } else {
+                if (it.telephonyDisplayInfo.overrideNetworkType != OVERRIDE_NETWORK_TYPE_NONE) {
                     OverrideNetworkType(
                         mobileMappingsProxy.toIconKeyOverride(
                             it.telephonyDisplayInfo.overrideNetworkType
                         )
                     )
+                } else if (it.telephonyDisplayInfo.networkType != NETWORK_TYPE_UNKNOWN) {
+                    DefaultNetworkType(
+                        mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
+                    )
+                } else {
+                    UnknownNetworkType
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
@@ -282,7 +283,10 @@
 
     override val cdmaRoaming: StateFlow<Boolean> =
         telephonyPollingEvent
-            .mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF }
+            .mapLatest {
+                val cdmaEri = telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
+                cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val networkName: StateFlow<NetworkNameModel> =
@@ -300,7 +304,8 @@
     override val dataEnabled = run {
         val initial = telephonyManager.isDataConnectionAllowed
         callbackEvents
-            .mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
+            .mapNotNull { it.onDataEnabledChanged }
+            .map { it.enabled }
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 
@@ -344,12 +349,41 @@
  * Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single
  * shared flow and then split them back out into other flows.
  */
-private sealed interface CallbackEvent {
+sealed interface CallbackEvent {
+    data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+    data class OnDataActivity(val direction: Int) : CallbackEvent
+    data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+    data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+    data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
     data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
     data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
-    data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
-    data class OnDataActivity(val direction: Int) : CallbackEvent
-    data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
-    data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
-    data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+}
+
+/**
+ * A simple box type for 1-to-1 mapping of [CallbackEvent] to the batched event. Used in conjunction
+ * with [scan] to make sure we don't drop important callbacks due to late subscribers
+ */
+data class TelephonyCallbackState(
+    val onDataActivity: CallbackEvent.OnDataActivity? = null,
+    val onCarrierNetworkChange: CallbackEvent.OnCarrierNetworkChange? = null,
+    val onDataConnectionStateChanged: CallbackEvent.OnDataConnectionStateChanged? = null,
+    val onDataEnabledChanged: CallbackEvent.OnDataEnabledChanged? = null,
+    val onDisplayInfoChanged: CallbackEvent.OnDisplayInfoChanged? = null,
+    val onServiceStateChanged: CallbackEvent.OnServiceStateChanged? = null,
+    val onSignalStrengthChanged: CallbackEvent.OnSignalStrengthChanged? = null,
+) {
+    fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
+        return when (event) {
+            is CallbackEvent.OnCarrierNetworkChange -> copy(onCarrierNetworkChange = event)
+            is CallbackEvent.OnDataActivity -> copy(onDataActivity = event)
+            is CallbackEvent.OnDataConnectionStateChanged ->
+                copy(onDataConnectionStateChanged = event)
+            is CallbackEvent.OnDataEnabledChanged -> copy(onDataEnabledChanged = event)
+            is CallbackEvent.OnDisplayInfoChanged -> copy(onDisplayInfoChanged = event)
+            is CallbackEvent.OnServiceStateChanged -> {
+                copy(onServiceStateChanged = event)
+            }
+            is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 3c7d092..95cc12a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1416,11 +1416,7 @@
             @Override
             public void onAnimationCancel(@NonNull Animator animation) {
                 mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL);
-                Log.i(TAG, "onAnimationCancel");
-
-                // We can only have one animation listener for cancel, so the jank listener should
-                // also call for cleanup.
-                finishDismiss();
+                Log.d(TAG, "onAnimationCancel");
             }
 
             @Override
@@ -1529,7 +1525,12 @@
                 .setDuration(mDialogHideAnimationDurationMs)
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
                 .withEndAction(() -> mHandler.postDelayed(() -> {
-                    finishDismiss();
+                    mController.notifyVisible(false);
+                    mDialog.dismiss();
+                    tryToRemoveCaptionsTooltip();
+                    mIsAnimatingDismiss = false;
+
+                    hideRingerDrawer();
                 }, 50));
         if (!shouldSlideInVolumeTray()) {
             animator.translationX(
@@ -1547,18 +1548,6 @@
         Trace.endSection();
     }
 
-    /**
-     * Clean up and hide volume dialog. Called when animation is finished/cancelled.
-     */
-    private void finishDismiss() {
-        mController.notifyVisible(false);
-        mDialog.dismiss();
-        tryToRemoveCaptionsTooltip();
-        mIsAnimatingDismiss = false;
-
-        hideRingerDrawer();
-    }
-
     private boolean showActiveStreamOnly() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
                 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 0e837d2..a35e5b5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -18,12 +18,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -37,6 +40,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
     @Mock
@@ -45,14 +49,14 @@
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
     private KeyguardMessageArea mKeyguardMessageArea;
-
     private KeyguardMessageAreaController mMessageAreaController;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mMessageAreaController = new KeyguardMessageAreaController.Factory(
-                mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea);
+                mKeyguardUpdateMonitor, mConfigurationController).create(
+                mKeyguardMessageArea);
     }
 
     @Test
@@ -89,6 +93,19 @@
     }
 
     @Test
+    public void testSetMessage_AnnounceForAccessibility() {
+        ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+        when(mKeyguardMessageArea.getText()).thenReturn("abc");
+        mMessageAreaController.setMessage("abc");
+
+        verify(mKeyguardMessageArea).setMessage("abc", /* animate= */ true);
+        verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class));
+        verify(mKeyguardMessageArea).postDelayed(argumentCaptor.capture(), anyLong());
+        argumentCaptor.getValue().run();
+        verify(mKeyguardMessageArea).announceForAccessibility("abc");
+    }
+
+    @Test
     public void testSetBouncerVisible() {
         mMessageAreaController.setIsVisible(true);
         verify(mKeyguardMessageArea).setIsVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 38d3a3e..f966eb3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -66,6 +66,7 @@
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -619,13 +620,26 @@
 
     @Test
     public void testReinflateViewFlipper() {
-        mKeyguardSecurityContainerController.reinflateViewFlipper();
+        mKeyguardSecurityContainerController.reinflateViewFlipper(() -> {});
         verify(mKeyguardSecurityViewFlipperController).clearViews();
         verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
                 any(KeyguardSecurityCallback.class));
     }
 
     @Test
+    public void testReinflateViewFlipper_asyncBouncerFlagOn() {
+        when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true);
+        KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener =
+                () -> {
+                };
+        mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedListener);
+        verify(mKeyguardSecurityViewFlipperController).clearViews();
+        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
+                any(SecurityMode.class),
+                any(KeyguardSecurityCallback.class), eq(onViewInflatedListener));
+    }
+
+    @Test
     public void testSideFpsControllerShow() {
         mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
         verify(mSideFpsController).show(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 1614b57..afb54d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -31,10 +31,12 @@
 import android.view.ViewGroup;
 import android.view.WindowInsetsController;
 
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -57,6 +59,8 @@
     @Mock
     private LayoutInflater mLayoutInflater;
     @Mock
+    private AsyncLayoutInflater mAsyncLayoutInflater;
+    @Mock
     private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
     @Mock
     private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
@@ -70,6 +74,8 @@
     private WindowInsetsController mWindowInsetsController;
     @Mock
     private KeyguardSecurityCallback mKeyguardSecurityCallback;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
 
@@ -82,10 +88,11 @@
         when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
         when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
                 .thenReturn(mEmergencyButtonController);
+        when(mView.getContext()).thenReturn(getContext());
 
         mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
-                mLayoutInflater, mKeyguardSecurityViewControllerFactory,
-                mEmergencyButtonControllerFactory);
+                mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory,
+                mEmergencyButtonControllerFactory, mFeatureFlags);
     }
 
     @Test
@@ -108,6 +115,14 @@
     }
 
     @Test
+    public void asynchronouslyInflateView() {
+        mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
+                mKeyguardSecurityCallback, null);
+        verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), any(
+                AsyncLayoutInflater.OnInflateFinishedListener.class));
+    }
+
+    @Test
     public void onDensityOrFontScaleChanged() {
         mKeyguardSecurityViewFlipperController.clearViews();
         verify(mView).removeAllViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
new file mode 100644
index 0000000..070cad7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.animation
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TAG_WGHT = "wght"
+private const val TAG_WDTH = "wdth"
+private const val TAG_OPSZ = "opsz"
+private const val TAG_ROND = "ROND"
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FontVariationUtilsTest : SysuiTestCase() {
+    @Test
+    fun testUpdateFontVariation_getCorrectFvarStr() {
+        val fontVariationUtils = FontVariationUtils()
+        val initFvar =
+            fontVariationUtils.updateFontVariation(
+                weight = 100,
+                width = 100,
+                opticalSize = -1,
+                roundness = 100
+            )
+        Assert.assertEquals("'$TAG_WGHT' 100, '$TAG_WDTH' 100, '$TAG_ROND' 100", initFvar)
+        val updatedFvar =
+            fontVariationUtils.updateFontVariation(
+                weight = 200,
+                width = 100,
+                opticalSize = 0,
+                roundness = 100
+            )
+        Assert.assertEquals(
+            "'$TAG_WGHT' 200, '$TAG_WDTH' 100, '$TAG_OPSZ' 0, '$TAG_ROND' 100",
+            updatedFvar
+        )
+    }
+
+    @Test
+    fun testStyleValueUnchange_getBlankStr() {
+        val fontVariationUtils = FontVariationUtils()
+        fontVariationUtils.updateFontVariation(
+            weight = 100,
+            width = 100,
+            opticalSize = 0,
+            roundness = 100
+        )
+        val updatedFvar1 =
+            fontVariationUtils.updateFontVariation(
+                weight = 100,
+                width = 100,
+                opticalSize = 0,
+                roundness = 100
+            )
+        Assert.assertEquals("", updatedFvar1)
+        val updatedFvar2 = fontVariationUtils.updateFontVariation()
+        Assert.assertEquals("", updatedFvar2)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index b389558..d7aa6e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -19,7 +19,6 @@
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.graphics.Typeface
-import android.graphics.fonts.FontVariationAxis
 import android.testing.AndroidTestingRunner
 import android.text.Layout
 import android.text.StaticLayout
@@ -180,71 +179,4 @@
 
         assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
     }
-
-    @Test
-    fun testSetTextStyle_addWeight() {
-        testWeightChange("", 100, FontVariationAxis.fromFontVariationSettings("'wght' 100")!!)
-    }
-
-    @Test
-    fun testSetTextStyle_changeWeight() {
-        testWeightChange(
-                "'wght' 500",
-                100,
-                FontVariationAxis.fromFontVariationSettings("'wght' 100")!!
-        )
-    }
-
-    @Test
-    fun testSetTextStyle_addWeightWithOtherAxis() {
-        testWeightChange(
-                "'wdth' 100",
-                100,
-                FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
-        )
-    }
-
-    @Test
-    fun testSetTextStyle_changeWeightWithOtherAxis() {
-        testWeightChange(
-                "'wght' 500, 'wdth' 100",
-                100,
-                FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
-        )
-    }
-
-    private fun testWeightChange(
-            initialFontVariationSettings: String,
-            weight: Int,
-            expectedFontVariationSettings: Array<FontVariationAxis>
-    ) {
-        val layout = makeLayout("Hello, World", PAINT)
-        val valueAnimator = mock(ValueAnimator::class.java)
-        val textInterpolator = mock(TextInterpolator::class.java)
-        val paint =
-                TextPaint().apply {
-                    typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
-                    fontVariationSettings = initialFontVariationSettings
-                }
-        `when`(textInterpolator.targetPaint).thenReturn(paint)
-
-        val textAnimator =
-                TextAnimator(layout, {}).apply {
-                    this.textInterpolator = textInterpolator
-                    this.animator = valueAnimator
-                }
-        textAnimator.setTextStyle(weight = weight, animate = false)
-
-        val resultFontVariationList =
-                FontVariationAxis.fromFontVariationSettings(
-                        textInterpolator.targetPaint.fontVariationSettings
-                )
-        expectedFontVariationSettings.forEach { expectedAxis ->
-            val resultAxis = resultFontVariationList?.filter { it.tag == expectedAxis.tag }?.get(0)
-            assertThat(resultAxis).isNotNull()
-            if (resultAxis != null) {
-                assertThat(resultAxis.styleValue).isEqualTo(expectedAxis.styleValue)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index e1c7417..c068efb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -35,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -930,6 +929,15 @@
         assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
     }
 
+    @Test
+    public void testCloseDialog_whenGlobalActionsMenuShown() throws Exception {
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+        mAuthController.handleShowGlobalActionsMenu();
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
+    }
+
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
         mAuthController.showAuthenticationDialog(createTestPromptInfo(),
                 mReceiver /* receiver */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
index 5a613aa..590989d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -45,6 +45,7 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -234,6 +235,36 @@
     }
 
     @Test
+    fun dialogPositiveButtonWhenCalledOnCompleteSettingIsTrue() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        doAnswer { assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue() }
+            .`when`(completedRunnable)
+            .invoke()
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun dialogPositiveCancelKeyguardStillCallsOnComplete() {
+        `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
+            .thenAnswer { (it.arguments[1] as Runnable).run() }
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
     fun dialogCancelDoesntChangeSetting() {
         sharedPreferences.putAttempts(0)
         secureSettings.putBool(SETTING_SHOW, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index b3329eb..0e16b47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.dreams.complication;
 
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -36,7 +35,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -57,8 +56,7 @@
     private Context mContext;
     @Mock
     private DreamBackend mDreamBackend;
-    @Mock
-    private SecureSettings mSecureSettings;
+    private FakeSettings mSecureSettings;
     @Mock
     private DreamOverlayStateController mDreamOverlayStateController;
     @Captor
@@ -74,6 +72,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
+        mSecureSettings = new FakeSettings();
 
         mMonitor = SelfExecutingMonitor.createInstance();
         mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor,
@@ -100,19 +99,15 @@
         when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>(Arrays.asList(
                 DreamBackend.COMPLICATION_TYPE_TIME, DreamBackend.COMPLICATION_TYPE_WEATHER,
                 DreamBackend.COMPLICATION_TYPE_AIR_QUALITY)));
-        final ContentObserver settingsObserver = captureSettingsObserver();
-        settingsObserver.onChange(false);
+
+        // Update the setting to trigger any content observers
+        mSecureSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, true,
+                UserHandle.myUserId());
         mExecutor.runAllReady();
 
         verify(mDreamOverlayStateController).setAvailableComplicationTypes(
                 Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_WEATHER
                         | Complication.COMPLICATION_TYPE_AIR_QUALITY);
     }
-
-    private ContentObserver captureSettingsObserver() {
-        verify(mSecureSettings).registerContentObserverForUser(
-                eq(Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED),
-                mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId()));
-        return mSettingsObserverCaptor.getValue();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 984f4be..1044131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -192,6 +192,7 @@
             )
         underTest.previewManager =
             KeyguardRemotePreviewManager(
+                applicationScope = testScope.backgroundScope,
                 previewRendererFactory = previewRendererFactory,
                 mainDispatcher = testDispatcher,
                 backgroundHandler = backgroundHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index e66be08..2ab1b99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -93,7 +93,7 @@
     }
 
     @Test
-    fun shouldUpdateSideFps() = runTest {
+    fun shouldUpdateSideFps_show() = runTest {
         var count = 0
         val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
         repository.setPrimaryShow(true)
@@ -104,6 +104,18 @@
     }
 
     @Test
+    fun shouldUpdateSideFps_hide() = runTest {
+        repository.setPrimaryShow(true)
+        var count = 0
+        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+        repository.setPrimaryShow(false)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(count).isEqualTo(1)
+        job.cancel()
+    }
+
+    @Test
     fun sideFpsShowing() = runTest {
         var sideFpsIsShowing = false
         val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index fd353af..df13fdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -94,7 +94,6 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -1763,7 +1762,7 @@
     fun tapContentView_showOverLockscreen_openActivity() {
         // WHEN we are on lockscreen and this activity can show over lockscreen
         whenever(keyguardStateController.isShowing).thenReturn(true)
-        whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(true)
+        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())).thenReturn(true)
 
         val clickIntent = mock(Intent::class.java)
         val pendingIntent = mock(PendingIntent::class.java)
@@ -1774,16 +1773,20 @@
         player.bindPlayer(data, KEY)
         verify(viewHolder.player).setOnClickListener(captor.capture())
 
-        // THEN it shows without dismissing keyguard first
+        // THEN it sends the PendingIntent without dismissing keyguard first,
+        // and does not use the Intent directly (see b/271845008)
         captor.value.onClick(viewHolder.player)
-        verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true))
+        verify(pendingIntent).send()
+        verify(pendingIntent, never()).getIntent()
+        verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
     }
 
     @Test
     fun tapContentView_noShowOverLockscreen_dismissKeyguard() {
         // WHEN we are on lockscreen and the activity cannot show over lockscreen
         whenever(keyguardStateController.isShowing).thenReturn(true)
-        whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(false)
+        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
+            .thenReturn(false)
 
         val clickIntent = mock(Intent::class.java)
         val pendingIntent = mock(PendingIntent::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 56e060d..17d8799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -16,12 +16,13 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
 import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
 import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
new file mode 100644
index 0000000..ceacaf9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.data.repository
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeRepositoryTest : SysuiTestCase() {
+
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun proxiedInput() = runTest {
+        val underTest = create()
+        val latest: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+
+        assertWithMessage("proxiedInput should start with null").that(latest).isNull()
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnTap)
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 100f))
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 100f))
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 120f))
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 120f))
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDragEnd)
+    }
+
+    @Test
+    fun shadeConfig_dualShadeEnabled() = runTest {
+        overrideResource(R.bool.dual_shade_enabled, true)
+        val underTest = create()
+        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+        assertThat(shadeConfig).isInstanceOf(ShadeConfig.DualShadeConfig::class.java)
+    }
+
+    @Test
+    fun shadeConfig_dualShadeNotEnabled() = runTest {
+        overrideResource(R.bool.dual_shade_enabled, false)
+        val underTest = create()
+        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+        assertThat(shadeConfig).isInstanceOf(ShadeConfig.SingleShadeConfig::class.java)
+    }
+
+    @Test
+    fun forceCollapseAll() = runTest {
+        val underTest = create()
+        val forceCollapseAll: Boolean? by collectLastValue(underTest.forceCollapseAll)
+
+        assertWithMessage("forceCollapseAll should start as false!")
+            .that(forceCollapseAll)
+            .isFalse()
+
+        underTest.setForceCollapseAll(true)
+        assertThat(forceCollapseAll).isTrue()
+
+        underTest.setForceCollapseAll(false)
+        assertThat(forceCollapseAll).isFalse()
+    }
+
+    @Test
+    fun shadeInteraction() = runTest {
+        val underTest = create()
+        val shadeInteraction: MultiShadeInteractionModel? by
+            collectLastValue(underTest.shadeInteraction)
+
+        assertWithMessage("shadeInteraction should start as null!").that(shadeInteraction).isNull()
+
+        underTest.setShadeInteraction(
+            MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false)
+        )
+        assertThat(shadeInteraction)
+            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false))
+
+        underTest.setShadeInteraction(
+            MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true)
+        )
+        assertThat(shadeInteraction)
+            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true))
+
+        underTest.setShadeInteraction(null)
+        assertThat(shadeInteraction).isNull()
+    }
+
+    @Test
+    fun expansion() = runTest {
+        val underTest = create()
+        val leftExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.LEFT).map { it.expansion })
+        val rightExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.RIGHT).map { it.expansion })
+        val singleExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.SINGLE).map { it.expansion })
+
+        assertWithMessage("expansion should start as 0!").that(leftExpansion).isZero()
+        assertWithMessage("expansion should start as 0!").that(rightExpansion).isZero()
+        assertWithMessage("expansion should start as 0!").that(singleExpansion).isZero()
+
+        underTest.setExpansion(
+            shadeId = ShadeId.LEFT,
+            0.4f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.4f)
+        assertThat(rightExpansion).isEqualTo(0f)
+        assertThat(singleExpansion).isEqualTo(0f)
+
+        underTest.setExpansion(
+            shadeId = ShadeId.RIGHT,
+            0.73f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.4f)
+        assertThat(rightExpansion).isEqualTo(0.73f)
+        assertThat(singleExpansion).isEqualTo(0f)
+
+        underTest.setExpansion(
+            shadeId = ShadeId.LEFT,
+            0.1f,
+        )
+        underTest.setExpansion(
+            shadeId = ShadeId.SINGLE,
+            0.88f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.1f)
+        assertThat(rightExpansion).isEqualTo(0.73f)
+        assertThat(singleExpansion).isEqualTo(0.88f)
+    }
+
+    private fun create(): MultiShadeRepository {
+        return create(
+            context = context,
+            inputProxy = inputProxy,
+        )
+    }
+
+    companion object {
+        fun create(
+            context: Context,
+            inputProxy: MultiShadeInputProxy,
+        ): MultiShadeRepository {
+            return MultiShadeRepository(
+                applicationContext = context,
+                inputProxy = inputProxy,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
new file mode 100644
index 0000000..415e68f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.domain.interactor
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepositoryTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeInteractorTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun maxShadeExpansion() =
+        testScope.runTest {
+            val underTest = create()
+            val maxShadeExpansion: Float? by collectLastValue(underTest.maxShadeExpansion)
+            assertWithMessage("maxShadeExpansion must start with 0.0!")
+                .that(maxShadeExpansion)
+                .isEqualTo(0f)
+
+            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
+            assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
+            assertThat(maxShadeExpansion).isEqualTo(0.442f)
+
+            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
+            assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
+            assertThat(maxShadeExpansion).isEqualTo(0f)
+        }
+
+    @Test
+    fun isVisible_dualShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val underTest = create()
+            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+            val isRightShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+            val isSingleShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+            assertThat(isLeftShadeVisible).isTrue()
+            assertThat(isRightShadeVisible).isTrue()
+            assertThat(isSingleShadeVisible).isFalse()
+        }
+
+    @Test
+    fun isVisible_singleShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val underTest = create()
+            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+            val isRightShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+            val isSingleShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+            assertThat(isLeftShadeVisible).isFalse()
+            assertThat(isRightShadeVisible).isFalse()
+            assertThat(isSingleShadeVisible).isTrue()
+        }
+
+    @Test
+    fun isNonProxiedInputAllowed() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeNonProxiedInputAllowed: Boolean? by
+                collectLastValue(underTest.isNonProxiedInputAllowed(ShadeId.LEFT))
+            assertWithMessage("isNonProxiedInputAllowed should start as true!")
+                .that(isLeftShadeNonProxiedInputAllowed)
+                .isTrue()
+
+            // Need to collect proxied input so the flows become hot as the gesture cancelation code
+            // logic sits in side the proxiedInput flow for each shade.
+            collectLastValue(underTest.proxiedInput(ShadeId.LEFT))
+            collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+
+            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+            // the
+            // same shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+            )
+            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+            // Registering the end of the proxied interaction re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+
+            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+            // disallowing non-proxied input on the LEFT shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+            )
+            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+        }
+
+    @Test
+    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+            // shade.
+            underTest.onUserInteractionStarted(ShadeId.RIGHT)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            underTest.onUserInteractionEnded(ShadeId.RIGHT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+            // shade.
+            underTest.onUserInteractionStarted(ShadeId.LEFT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the LEFT shade re-allows it.
+            underTest.onUserInteractionEnded(ShadeId.LEFT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun collapseAll() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            underTest.collapseAll()
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+
+            // Receiving proxied input on that's not a tap gesture, on the left-hand side resets the
+            // "collapse all". Note that now the RIGHT shade is force-collapsed because we're
+            // interacting with the LEFT shade.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 0f))
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun onTapOutside_collapsesAll() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+        }
+
+    @Test
+    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+        testScope.runTest {
+            val underTest = create()
+            val proxiedInput: ProxiedInputModel? by
+                collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+            underTest.onUserInteractionStarted(shadeId = ShadeId.RIGHT)
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNull()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+            assertThat(proxiedInput).isNull()
+
+            underTest.onUserInteractionEnded(shadeId = ShadeId.RIGHT)
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNotNull()
+        }
+
+    private fun create(): MultiShadeInteractor {
+        return create(
+            testScope = testScope,
+            context = context,
+            inputProxy = inputProxy,
+        )
+    }
+
+    companion object {
+        fun create(
+            testScope: TestScope,
+            context: Context,
+            inputProxy: MultiShadeInputProxy,
+        ): MultiShadeInteractor {
+            return MultiShadeInteractor(
+                applicationScope = testScope.backgroundScope,
+                repository =
+                    MultiShadeRepositoryTest.create(
+                        context = context,
+                        inputProxy = inputProxy,
+                    ),
+                inputProxy = inputProxy,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
new file mode 100644
index 0000000..0484515
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeViewModelTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun scrim_whenDualShadeCollapsed() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, true)
+
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    @Test
+    fun scrim_whenDualShadeExpanded() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+
+            underTest.leftShade.onExpansionChanged(0.5f)
+            assertThat(scrimAlpha).isEqualTo(alpha * 0.5f)
+            assertThat(isScrimEnabled).isTrue()
+
+            underTest.rightShade.onExpansionChanged(1f)
+            assertThat(scrimAlpha).isEqualTo(alpha * 1f)
+            assertThat(isScrimEnabled).isTrue()
+        }
+
+    @Test
+    fun scrim_whenSingleShadeCollapsed() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, false)
+
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    @Test
+    fun scrim_whenSingleShadeExpanded() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            underTest.singleShade.onExpansionChanged(0.95f)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    private fun create(): MultiShadeViewModel {
+        return MultiShadeViewModel(
+            viewModelScope = testScope.backgroundScope,
+            interactor =
+                MultiShadeInteractorTest.create(
+                    testScope = testScope,
+                    context = context,
+                    inputProxy = inputProxy,
+                ),
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
new file mode 100644
index 0000000..e32aac5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ShadeViewModelTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+    private var interactor: MultiShadeInteractor? = null
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun isVisible_dualShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+            assertThat(isLeftShadeVisible).isTrue()
+            assertThat(isRightShadeVisible).isTrue()
+            assertThat(isSingleShadeVisible).isFalse()
+        }
+
+    @Test
+    fun isVisible_singleShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+            assertThat(isLeftShadeVisible).isFalse()
+            assertThat(isRightShadeVisible).isFalse()
+            assertThat(isSingleShadeVisible).isTrue()
+        }
+
+    @Test
+    fun isSwipingEnabled() =
+        testScope.runTest {
+            val underTest = create(ShadeId.LEFT)
+            val isSwipingEnabled: Boolean? by collectLastValue(underTest.isSwipingEnabled)
+            assertWithMessage("isSwipingEnabled should start as true!")
+                .that(isSwipingEnabled)
+                .isTrue()
+
+            // Need to collect proxied input so the flows become hot as the gesture cancelation code
+            // logic sits in side the proxiedInput flow for each shade.
+            collectLastValue(underTest.proxiedInput)
+            collectLastValue(create(ShadeId.RIGHT).proxiedInput)
+
+            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+            // the
+            // same shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+            )
+            assertThat(isSwipingEnabled).isFalse()
+
+            // Registering the end of the proxied interaction re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isSwipingEnabled).isTrue()
+
+            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+            // disallowing non-proxied input on the LEFT shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+            )
+            assertThat(isSwipingEnabled).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isSwipingEnabled).isTrue()
+        }
+
+    @Test
+    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+        testScope.runTest {
+            val leftShade = create(ShadeId.LEFT)
+            val rightShade = create(ShadeId.RIGHT)
+            val isLeftShadeForceCollapsed: Boolean? by collectLastValue(leftShade.isForceCollapsed)
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(rightShade.isForceCollapsed)
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+            // shade.
+            rightShade.onDragStarted()
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            rightShade.onDragEnded()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+            // shade.
+            leftShade.onDragStarted()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the LEFT shade re-allows it.
+            leftShade.onDragEnded()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun onTapOutside_collapsesAll() =
+        testScope.runTest {
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.LEFT).isForceCollapsed)
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.RIGHT).isForceCollapsed)
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+        }
+
+    @Test
+    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+        testScope.runTest {
+            val underTest = create(ShadeId.RIGHT)
+            val proxiedInput: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+            underTest.onDragStarted()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNull()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+            assertThat(proxiedInput).isNull()
+
+            underTest.onDragEnded()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNotNull()
+        }
+
+    private fun create(
+        shadeId: ShadeId,
+    ): ShadeViewModel {
+        return ShadeViewModel(
+            viewModelScope = testScope.backgroundScope,
+            shadeId = shadeId,
+            interactor = interactor
+                    ?: MultiShadeInteractorTest.create(
+                            testScope = testScope,
+                            context = context,
+                            inputProxy = inputProxy,
+                        )
+                        .also { interactor = it },
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 51492eb..0dc2d17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -29,6 +29,8 @@
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -112,6 +114,9 @@
             .thenReturn(keyguardSecurityContainerController)
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
+
+        val featureFlags = FakeFeatureFlags();
+        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
         underTest =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -138,6 +143,7 @@
                 udfpsOverlayInteractor,
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                featureFlags,
             )
         underTest.setupExpandedStatusBar()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 2f528a8..2797440 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -43,6 +43,8 @@
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -132,6 +134,8 @@
         when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition())
                 .thenReturn(emptyFlow());
 
+        FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false);
         mController = new NotificationShadeWindowViewController(
                 mLockscreenShadeTransitionController,
                 new FalsingCollectorFake(),
@@ -156,7 +160,8 @@
                 mAlternateBouncerInteractor,
                 mUdfpsOverlayInteractor,
                 mKeyguardTransitionInteractor,
-                mPrimaryBouncerToGoneTransitionViewModel
+                mPrimaryBouncerToGoneTransitionViewModel,
+                featureFlags
         );
         mController.setupExpandedStatusBar();
         mController.setDragDownHelper(mDragDownHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 64e58d0..0e2a3ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -134,7 +134,7 @@
 
         TransitionInfoBuilder(@WindowManager.TransitionType int type) {
             mInfo = new TransitionInfo(type, 0 /* flags */);
-            mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+            mInfo.addRootLeash(0, createMockSurface(true /* valid */), 0, 0);
         }
 
         TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -144,6 +144,7 @@
             change.setMode(mode);
             change.setFlags(flags);
             change.setTaskInfo(taskInfo);
+            change.setDisplayId(0, 0);
             mInfo.addChange(change);
             return this;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index a280510..58b44ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper
 import android.view.View
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController
@@ -46,6 +47,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
 import org.mockito.MockitoAnnotations
 import org.mockito.Spy
 
@@ -69,12 +71,21 @@
     private lateinit var viewComponent: SmartspaceViewComponent
 
     @Mock
+    private lateinit var weatherViewComponent: SmartspaceViewComponent
+
+    @Spy
+    private var weatherSmartspaceView: SmartspaceView = TestView(context)
+
+    @Mock
     private lateinit var targetFilter: SmartspaceTargetFilter
 
     @Mock
     private lateinit var plugin: BcSmartspaceDataPlugin
 
     @Mock
+    private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+
+    @Mock
     private lateinit var precondition: SmartspacePrecondition
 
     @Spy
@@ -88,6 +99,9 @@
 
     private lateinit var controller: DreamSmartspaceController
 
+    // TODO(b/272811280): Remove usage of real view
+    private val fakeParent = FrameLayout(context)
+
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
      * object inheritance and interface implementation used in DreamSmartspaceController
@@ -121,13 +135,17 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        `when`(viewComponentFactory.create(any(), eq(plugin), any()))
+        `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
                 .thenReturn(viewComponent)
         `when`(viewComponent.getView()).thenReturn(smartspaceView)
+        `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
+            .thenReturn(weatherViewComponent)
+        `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
 
         controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
-                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin))
+                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
+        Optional.of(weatherPlugin))
     }
 
     /**
@@ -168,11 +186,11 @@
         `when`(precondition.conditionsMet()).thenReturn(true)
         controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
 
-        var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
-            verify(viewComponentFactory).create(any(), eq(plugin), capture())
+        val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
+            verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
         }
 
-        var mockView = Mockito.mock(TestView::class.java)
+        val mockView = Mockito.mock(TestView::class.java)
         `when`(precondition.conditionsMet()).thenReturn(true)
         stateChangeListener.onViewAttachedToWindow(mockView)
 
@@ -183,4 +201,74 @@
 
         verify(session).close()
     }
+
+    /**
+     * Ensures session is created when weather smartspace view is created and attached.
+     */
+    @Test
+    fun testConnectOnWeatherViewCreate() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        val customView = Mockito.mock(TestView::class.java)
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherSmartspaceView = weatherView as SmartspaceView
+        fakeParent.addView(weatherView)
+
+        // Then weather view is created with custom view and the default weatherPlugin.getView
+        // should not be called
+        verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
+            eq(customView))
+        verify(weatherPlugin, Mockito.never()).getView(fakeParent)
+
+        // And then session is created
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+        verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(weatherSmartspaceView).setDozeAmount(0f)
+    }
+
+    /**
+     * Ensures weather plugin registers target listener when it is added from the controller.
+     */
+    @Test
+    fun testAddListenerInController_registersListenerForWeatherPlugin() {
+        val customView = Mockito.mock(TestView::class.java)
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        // Given a session is created
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        // When a listener is added
+        controller.addListenerForWeatherPlugin(listener)
+
+        // Then the listener is registered to the weather plugin only
+        verify(weatherPlugin).registerListener(listener)
+        verify(plugin, Mockito.never()).registerListener(any())
+    }
+
+    /**
+     * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
+     * view is detached.
+     */
+    @Test
+    fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        // Given a session is created
+        val customView = Mockito.mock(TestView::class.java)
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        // When view is detached
+        controller.stateChangeListener.onViewDetachedFromWindow(weatherView)
+        // Then the session is closed
+        verify(session).close()
+
+        // And the listener receives an empty list of targets and unregisters the notifier
+        verify(weatherPlugin).onTargetsAvailable(emptyList())
+        verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index cb4f119..4bb14a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
@@ -58,6 +59,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.BDDMockito.clearInvocations
 import org.mockito.BDDMockito.given
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -166,6 +168,12 @@
         mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
         mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
         mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
+
+        // Set the default FSI decision
+        setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+
+        // Run tests with default feature flag state
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default)
     }
 
     @Test
@@ -810,6 +818,39 @@
     }
 
     @Test
+    fun onEntryAdded_whenLaunchingFSI_doesLogDecision() {
+        // GIVEN A new notification can FSI
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+    }
+
+    @Test
+    fun onEntryAdded_whenNotLaunchingFSI_doesLogDecision() {
+        // GIVEN A new notification can't FSI
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+    }
+
+    @Test
+    fun onEntryAdded_whenNotLaunchingFSIBecauseOfDnd_doesLogDecision() {
+        // GIVEN A new notification can't FSI because of DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+    }
+
+    @Test
     fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() {
         // Ensure the feature flag is off
         whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false)
@@ -818,13 +859,22 @@
         setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
         mCollectionListener.onEntryAdded(mEntry)
 
+        // Verify that this causes a log
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        clearInvocations(mNotificationInterruptStateProvider)
+
         // and it is then updated to allow full screen
         setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
         whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
         mCollectionListener.onRankingApplied()
 
         // THEN it should not full screen because the feature is off
-        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+
+        // VERIFY that no additional logging happens either
+        verify(mNotificationInterruptStateProvider, never())
+                .logFullScreenIntentDecision(any(), any())
     }
 
     @Test
@@ -836,8 +886,11 @@
         setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
         mCollectionListener.onEntryAdded(mEntry)
 
-        // at this point, it should not have full screened
-        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+        // at this point, it should not have full screened, but should have logged
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        clearInvocations(mNotificationInterruptStateProvider)
 
         // and it is then updated to allow full screen AND HUN
         setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
@@ -847,10 +900,110 @@
         mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
         mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
 
-        // THEN it should full screen but it should NOT HUN
+        // THEN it should full screen and log but it should NOT HUN
         verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
         verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
         verify(mHeadsUpManager, never()).showNotification(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        clearInvocations(mNotificationInterruptStateProvider)
+
+        // WHEN ranking updates again and the pipeline reruns
+        clearInvocations(mLaunchFullScreenIntentProvider)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // VERIFY that the FSI does not launch again or log
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider, never())
+                .logFullScreenIntentDecision(any(), any())
+    }
+
+    @Test
+    fun testOnRankingApplied_withOnlyDndSuppressionAllowsFsiLater() {
+        // Turn on the feature
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // at this point, it should not have full screened, but should have logged
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        clearInvocations(mNotificationInterruptStateProvider)
+
+        // ranking is applied with only DND blocking FSI
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should still not yet full screen or HUN
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+
+        // Same decision as before; is not logged
+        verify(mNotificationInterruptStateProvider, never())
+                .logFullScreenIntentDecision(any(), any())
+        clearInvocations(mNotificationInterruptStateProvider)
+
+        // and it is then updated to allow full screen AND HUN
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        setShouldHeadsUp(mEntry)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should full screen and log but it should NOT HUN
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        clearInvocations(mNotificationInterruptStateProvider)
+    }
+
+    @Test
+    fun testOnRankingApplied_newNonFullScreenAnswerInvalidatesCandidate() {
+        // Turn on the feature
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // at this point, it should not have full screened
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+
+        // now some other condition blocks FSI in addition to DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should NOT full screen or HUN
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+
+        // NOW the DND logic changes and FSI and HUN are available
+        clearInvocations(mLaunchFullScreenIntentProvider)
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        setShouldHeadsUp(mEntry)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // VERIFY that the FSI didn't happen, but that we do HUN
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        finishBind(mEntry)
+        verify(mHeadsUpManager).showNotification(mEntry)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 07d0dbd..8acf507 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -36,9 +36,12 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -76,6 +79,10 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Tests for the interruption state provider which understands whether the system & notification
  * is in a state allowing a particular notification to hun, pulse, or bubble.
@@ -560,7 +567,7 @@
                 .isFalse();
         verify(mLogger, never()).logFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUPPRESSED_ONLY_BY_DND");
     }
 
     @Test
@@ -579,7 +586,7 @@
                 .isFalse();
         verify(mLogger, never()).logFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUPPRESSED_BY_DND");
     }
 
     @Test
@@ -599,7 +606,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Not important enough");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_NOT_IMPORTANT_ENOUGH");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -622,7 +629,8 @@
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
         verify(mLogger, never()).logNoFullscreen(any(), any());
-        verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+        verify(mLogger).logNoFullscreenWarning(entry,
+                "NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: GroupAlertBehavior will prevent HUN");
         verify(mLogger, never()).logFullscreen(any(), any());
 
         assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
@@ -652,7 +660,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Device is not interactive");
+        verify(mLogger).logFullscreen(entry, "FSI_DEVICE_NOT_INTERACTIVE");
     }
 
     @Test
@@ -674,7 +682,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Device is dreaming");
+        verify(mLogger).logFullscreen(entry, "FSI_DEVICE_IS_DREAMING");
     }
 
     @Test
@@ -696,7 +704,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Keyguard is showing");
+        verify(mLogger).logFullscreen(entry, "FSI_KEYGUARD_SHOWING");
     }
 
     @Test
@@ -717,7 +725,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -737,7 +745,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Expected not to HUN");
+        verify(mLogger).logFullscreen(entry, "FSI_EXPECTED_NOT_TO_HUN");
     }
 
     @Test
@@ -756,7 +764,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -802,7 +810,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -848,12 +856,37 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
 
     @Test
+    public void logFullScreenIntentDecision_shouldAlmostAlwaysLogOneTime() {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList(
+                FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+                FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD
+        ));
+        for (FullScreenIntentDecision decision : FullScreenIntentDecision.values()) {
+            clearInvocations(mLogger);
+            boolean expectedToLog = decision != FullScreenIntentDecision.NO_FULL_SCREEN_INTENT;
+            boolean isWarning = warnings.contains(decision);
+            mNotifInterruptionStateProvider.logFullScreenIntentDecision(entry, decision);
+            if (decision.shouldLaunch) {
+                verify(mLogger).logFullscreen(eq(entry), contains(decision.name()));
+            } else if (expectedToLog) {
+                if (isWarning) {
+                    verify(mLogger).logNoFullscreenWarning(eq(entry), contains(decision.name()));
+                } else {
+                    verify(mLogger).logNoFullscreen(eq(entry), contains(decision.name()));
+                }
+            }
+            verifyNoMoreInteractions(mLogger);
+        }
+    }
+
+    @Test
     public void testShouldHeadsUp_snoozed_unlocked_withStrictRules() throws Exception {
         when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index f6e5959..542b688 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -18,17 +18,16 @@
 
 import android.content.Intent
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
-import android.telephony.CellSignalStrengthCdma
 import android.telephony.NetworkRegistrationInfo
 import android.telephony.ServiceState
 import android.telephony.ServiceState.STATE_IN_SERVICE
 import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
-import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.DataActivityListener
 import android.telephony.TelephonyCallback.ServiceStateListener
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
 import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
 import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
@@ -68,6 +67,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -75,14 +75,12 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
@@ -99,7 +97,6 @@
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
 
-    private val scope = CoroutineScope(IMMEDIATE)
     private val mobileMappings = FakeMobileMappingsProxy()
     private val systemUiCarrierConfig =
         SystemUiCarrierConfig(
@@ -107,6 +104,9 @@
             createTestConfig(),
         )
 
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -124,21 +124,16 @@
                 systemUiCarrierConfig,
                 fakeBroadcastDispatcher,
                 mobileMappings,
-                IMMEDIATE,
+                testDispatcher,
                 logger,
                 tableLogger,
-                scope,
+                testScope.backgroundScope,
             )
     }
 
-    @After
-    fun tearDown() {
-        scope.cancel()
-    }
-
     @Test
     fun emergencyOnly() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
 
@@ -154,18 +149,15 @@
 
     @Test
     fun emergencyOnly_toggles() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<ServiceStateListener>()
-            val serviceState = ServiceState()
-            serviceState.isEmergencyOnly = true
-            callback.onServiceStateChanged(serviceState)
+            callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = true })
             assertThat(latest).isTrue()
 
-            serviceState.isEmergencyOnly = false
-            callback.onServiceStateChanged(serviceState)
+            callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = false })
 
             assertThat(latest).isFalse()
 
@@ -174,7 +166,7 @@
 
     @Test
     fun cdmaLevelUpdates() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
 
@@ -194,7 +186,7 @@
 
     @Test
     fun gsmLevelUpdates() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
@@ -214,7 +206,7 @@
 
     @Test
     fun isGsm() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
 
@@ -234,7 +226,7 @@
 
     @Test
     fun dataConnectionState_connected() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -249,7 +241,7 @@
 
     @Test
     fun dataConnectionState_connecting() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -264,7 +256,7 @@
 
     @Test
     fun dataConnectionState_disconnected() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -279,7 +271,7 @@
 
     @Test
     fun dataConnectionState_disconnecting() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -294,7 +286,7 @@
 
     @Test
     fun dataConnectionState_suspended() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -309,7 +301,7 @@
 
     @Test
     fun dataConnectionState_handoverInProgress() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -324,7 +316,7 @@
 
     @Test
     fun dataConnectionState_unknown() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -339,7 +331,7 @@
 
     @Test
     fun dataConnectionState_invalid() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -354,7 +346,7 @@
 
     @Test
     fun dataActivity() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
 
@@ -368,7 +360,7 @@
 
     @Test
     fun carrierNetworkChange() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
 
@@ -382,7 +374,7 @@
 
     @Test
     fun networkType_default() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
@@ -395,7 +387,7 @@
 
     @Test
     fun networkType_unknown_hasCorrectKey() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
@@ -413,14 +405,19 @@
 
     @Test
     fun networkType_updatesUsingDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+            val overrideType = OVERRIDE_NETWORK_TYPE_NONE
             val type = NETWORK_TYPE_LTE
             val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
-            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            val ti =
+                mock<TelephonyDisplayInfo>().also {
+                    whenever(it.overrideNetworkType).thenReturn(overrideType)
+                    whenever(it.networkType).thenReturn(type)
+                }
             callback.onDisplayInfoChanged(ti)
 
             assertThat(latest).isEqualTo(expected)
@@ -430,7 +427,7 @@
 
     @Test
     fun networkType_updatesUsingOverride() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
@@ -450,16 +447,38 @@
         }
 
     @Test
+    fun networkType_unknownNetworkWithOverride_usesOverrideKey() =
+        testScope.runTest {
+            var latest: ResolvedNetworkType? = null
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+            val unknown = NETWORK_TYPE_UNKNOWN
+            val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
+            val ti =
+                mock<TelephonyDisplayInfo>().also {
+                    whenever(it.networkType).thenReturn(unknown)
+                    whenever(it.overrideNetworkType).thenReturn(type)
+                }
+            callback.onDisplayInfoChanged(ti)
+
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
     fun dataEnabled_initial_false() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
 
             assertThat(underTest.dataEnabled.value).isFalse()
         }
 
     @Test
-    fun `is data enabled - tracks telephony callback`() =
-        runBlocking(IMMEDIATE) {
+    fun isDataEnabled_tracksTelephonyCallback() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
 
@@ -479,7 +498,7 @@
 
     @Test
     fun numberOfLevels_isDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
 
@@ -489,51 +508,68 @@
         }
 
     @Test
-    fun `roaming - cdma - queries telephony manager`() =
-        runBlocking(IMMEDIATE) {
+    fun roaming_cdma_queriesTelephonyManager() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
 
             val cb = getTelephonyCallbackForType<ServiceStateListener>()
 
-            val serviceState = ServiceState()
-            serviceState.roaming = false
-
-            // CDMA roaming is off, GSM roaming is off
+            // CDMA roaming is off, GSM roaming is on
             whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
-            cb.onServiceStateChanged(serviceState)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
 
             assertThat(latest).isFalse()
 
-            // CDMA roaming is off, GSM roaming is on
+            // CDMA roaming is on, GSM roaming is off
             whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
-            cb.onServiceStateChanged(serviceState)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
 
             assertThat(latest).isTrue()
 
             job.cancel()
         }
 
+    /**
+     * [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber] returns -1 if the service is
+     * not running or if there is an error while retrieving the cdma ERI
+     */
     @Test
-    fun `roaming - gsm - queries service state`() =
-        runBlocking(IMMEDIATE) {
+    fun cdmaRoaming_ignoresNegativeOne() =
+        testScope.runTest {
             var latest: Boolean? = null
-            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+            val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
 
             val serviceState = ServiceState()
             serviceState.roaming = false
 
             val cb = getTelephonyCallbackForType<ServiceStateListener>()
 
-            // CDMA roaming is off, GSM roaming is off
-            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            // CDMA roaming is unavailable (-1), GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(-1)
             cb.onServiceStateChanged(serviceState)
 
             assertThat(latest).isFalse()
 
+            job.cancel()
+        }
+
+    @Test
+    fun roaming_gsm_queriesServiceState() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+            val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+            // CDMA roaming is off, GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
+
+            assertThat(latest).isFalse()
+
             // CDMA roaming is off, GSM roaming is on
-            serviceState.roaming = true
-            cb.onServiceStateChanged(serviceState)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
 
             assertThat(latest).isTrue()
 
@@ -541,8 +577,8 @@
         }
 
     @Test
-    fun `activity - updates from callback`() =
-        runBlocking(IMMEDIATE) {
+    fun activity_updatesFromCallback() =
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
 
@@ -578,8 +614,8 @@
         }
 
     @Test
-    fun `network name - default`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_default() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -589,8 +625,8 @@
         }
 
     @Test
-    fun `network name - uses broadcast info - returns derived`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_usesBroadcastInfo_returnsDerived() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -606,8 +642,8 @@
         }
 
     @Test
-    fun `network name - broadcast not for this sub id - keeps old value`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_broadcastNotForThisSubId_keepsOldValue() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -631,8 +667,8 @@
         }
 
     @Test
-    fun `network name - broadcast has no data - updates to default`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_broadcastHasNoData_updatesToDefault() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -658,8 +694,8 @@
         }
 
     @Test
-    fun `operatorAlphaShort - tracked`() =
-        runBlocking(IMMEDIATE) {
+    fun operatorAlphaShort_tracked() =
+        testScope.runTest {
             var latest: String? = null
 
             val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
@@ -680,33 +716,45 @@
         }
 
     @Test
-    fun `connection model - isInService - not iwlan`() =
-        runBlocking(IMMEDIATE) {
+    fun isInService_notIwlan() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
-            val serviceState = ServiceState()
-            serviceState.voiceRegState = STATE_IN_SERVICE
-            serviceState.dataRegState = STATE_IN_SERVICE
-
-            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(
+                    ServiceState().also {
+                        it.voiceRegState = STATE_IN_SERVICE
+                        it.dataRegState = STATE_IN_SERVICE
+                    }
+                )
 
             assertThat(latest).isTrue()
 
-            serviceState.voiceRegState = STATE_OUT_OF_SERVICE
-            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(
+                    ServiceState().also {
+                        it.dataRegState = STATE_IN_SERVICE
+                        it.voiceRegState = STATE_OUT_OF_SERVICE
+                    }
+                )
             assertThat(latest).isTrue()
 
-            serviceState.dataRegState = STATE_OUT_OF_SERVICE
-            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(
+                    ServiceState().also {
+                        it.voiceRegState = STATE_OUT_OF_SERVICE
+                        it.dataRegState = STATE_OUT_OF_SERVICE
+                    }
+                )
             assertThat(latest).isFalse()
 
             job.cancel()
         }
 
     @Test
-    fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
-        runBlocking(IMMEDIATE) {
+    fun isInService_isIwlan_voiceOutOfService_dataInService() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
@@ -730,8 +778,8 @@
         }
 
     @Test
-    fun `number of levels - uses carrier config`() =
-        runBlocking(IMMEDIATE) {
+    fun numberOfLevels_usesCarrierConfig() =
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
 
@@ -756,19 +804,6 @@
         return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
     }
 
-    /** Convenience constructor for SignalStrength */
-    private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
-        val signalStrength = mock<SignalStrength>()
-        whenever(signalStrength.isGsm).thenReturn(isGsm)
-        whenever(signalStrength.level).thenReturn(gsmLevel)
-        val cdmaStrength =
-            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
-        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
-            .thenReturn(listOf(cdmaStrength))
-
-        return signalStrength
-    }
-
     private fun spnIntent(
         subId: Int = SUB_1_ID,
         showSpn: Boolean = true,
@@ -785,7 +820,6 @@
         }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
 
         private val DEFAULT_NAME = NetworkNameModel.Default("default name")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
new file mode 100644
index 0000000..bbf04ed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+
+import android.telephony.ServiceState
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DataEnabledListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/**
+ * Test class to stress test the TelephonyCallbacks that we listen to. In particular, the callbacks
+ * all come back in on a single listener (for reasons defined in the system). This test is built to
+ * ensure that we don't miss any important callbacks.
+ *
+ * Kind of like an interaction test case build just for [TelephonyCallback]
+ *
+ * The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener]
+ * [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener]
+ * [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener]
+ * [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener]
+ *
+ * Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed
+ * by only a single callback can immediately create backpressure on the other fields related to a
+ * mobile connection.
+ *
+ * This test should be designed to test _at least_ each individual callback in a smoke-test fashion.
+ * The way we will achieve this is as follows:
+ * 1. Start up a listener (A) collecting on a field which is _not under test_
+ * 2. Send a single event to a telephony callback which supports the field under test (B)
+ * 3. Send many (may be as few as 2) events to the callback backing A to ensure we start seeing
+ *    backpressure on other fields NOTE: poor handling of backpressure here would normally cause B
+ *    to get dropped
+ * 4. Start up a new collector for B
+ * 5. Assert that B has the state sent in step #2
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
+    private lateinit var underTest: MobileConnectionRepositoryImpl
+    private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var logger: MobileInputLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
+
+    private val mobileMappings = FakeMobileMappingsProxy()
+    private val systemUiCarrierConfig =
+        SystemUiCarrierConfig(
+            SUB_1_ID,
+            SystemUiCarrierConfigTest.createTestConfig(),
+        )
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
+
+        connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
+
+        underTest =
+            MobileConnectionRepositoryImpl(
+                context,
+                SUB_1_ID,
+                DEFAULT_NAME,
+                SEP,
+                telephonyManager,
+                systemUiCarrierConfig,
+                fakeBroadcastDispatcher,
+                mobileMappings,
+                testDispatcher,
+                logger,
+                tableLogger,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun carrierNetworkChangeListener_noisyActivity() =
+        testScope.runTest {
+            var latest: Boolean? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+            callback.onCarrierNetworkChange(true)
+
+            flipActivity(100, activityCallback)
+
+            val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun dataActivityLate_noisyDisplayInfo() =
+        testScope.runTest {
+            var latest: DataActivityModel? = null
+
+            // start collecting displayInfo; don't care about the result
+            val displayInfoJob = underTest.resolvedNetworkType.launchIn(this)
+
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+            activityCallback.onDataActivity(DATA_ACTIVITY_INOUT)
+
+            val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
+            val type1 = NETWORK_TYPE_UNKNOWN
+            val type2 = NETWORK_TYPE_LTE
+            val t1 =
+                mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type1) }
+            val t2 =
+                mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type2) }
+
+            flipDisplayInfo(100, listOf(t1, t2), displayInfoCallback)
+
+            val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest)
+                .isEqualTo(
+                    DataActivityModel(
+                        hasActivityIn = true,
+                        hasActivityOut = true,
+                    )
+                )
+
+            displayInfoJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun dataConnectionStateListener_noisyActivity() =
+        testScope.runTest {
+            var latest: DataConnectionState? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val connectionCallback = getTelephonyCallbackForType<DataConnectionStateListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            connectionCallback.onDataConnectionStateChanged(
+                TelephonyManager.DATA_CONNECTED,
+                200 /* unused */
+            )
+
+            // Send a bunch of events that we don't care about, to overrun the replay buffer
+            flipActivity(100, activityCallback)
+
+            val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(DataConnectionState.Connected)
+
+            activityJob.cancel()
+            connectionJob.cancel()
+        }
+
+    @Test
+    fun dataEnabledLate_noisyActivity() =
+        testScope.runTest {
+            var latest: Boolean? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val enabledCallback = getTelephonyCallbackForType<DataEnabledListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            enabledCallback.onDataEnabledChanged(true, 1 /* unused */)
+
+            // Send a bunch of events that we don't care about, to overrun the replay buffer
+            flipActivity(100, activityCallback)
+
+            val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun displayInfoLate_noisyActivity() =
+        testScope.runTest {
+            var latest: ResolvedNetworkType? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            val type = NETWORK_TYPE_LTE
+            val expected = ResolvedNetworkType.DefaultNetworkType(mobileMappings.toIconKey(type))
+            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            displayInfoCallback.onDisplayInfoChanged(ti)
+
+            // Send a bunch of events that we don't care about, to overrun the replay buffer
+            flipActivity(100, activityCallback)
+
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(expected)
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun serviceStateListener_noisyActivity() =
+        testScope.runTest {
+            var latest: Boolean? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val serviceStateCallback = getTelephonyCallbackForType<ServiceStateListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            // isEmergencyOnly comes in
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+            serviceStateCallback.onServiceStateChanged(serviceState)
+
+            flipActivity(100, activityCallback)
+
+            val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun signalStrengthsListenerLate_noisyActivity() =
+        testScope.runTest {
+            var latest: Int? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+            val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+            callback.onSignalStrengthsChanged(strength)
+
+            flipActivity(100, activityCallback)
+
+            val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(2)
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    private fun flipActivity(
+        times: Int,
+        callback: DataActivityListener,
+    ) {
+        repeat(times) { index -> callback.onDataActivity(index % 4) }
+    }
+
+    private fun flipDisplayInfo(
+        times: Int,
+        infos: List<TelephonyDisplayInfo>,
+        callback: DisplayInfoListener,
+    ) {
+        val len = infos.size
+        repeat(times) { index -> callback.onDisplayInfoChanged(infos[index % len]) }
+    }
+
+    private inline fun <reified T> getTelephonyCallbackForType(): T {
+        return getTelephonyCallbackForType(telephonyManager)
+    }
+
+    companion object {
+        private const val SUB_1_ID = 1
+
+        private val DEFAULT_NAME = NetworkNameModel.Default("default name")
+        private const val SEP = "-"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index 621f793..d07b96f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.mockito.Mockito.verify
 
@@ -31,6 +35,19 @@
         return callbackCaptor.allValues
     }
 
+    /** Convenience constructor for SignalStrength */
+    fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+        val signalStrength = mock<SignalStrength>()
+        whenever(signalStrength.isGsm).thenReturn(isGsm)
+        whenever(signalStrength.level).thenReturn(gsmLevel)
+        val cdmaStrength =
+            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+            .thenReturn(listOf(cdmaStrength))
+
+        return signalStrength
+    }
+
     inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
         val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
         assertThat(cbs.size).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
new file mode 100644
index 0000000..71bd511
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseShaderTest : SysuiTestCase() {
+
+    private lateinit var turbulenceNoiseShader: TurbulenceNoiseShader
+
+    @Test
+    fun compliesSimplexNoise() {
+        turbulenceNoiseShader = TurbulenceNoiseShader()
+    }
+
+    @Test
+    fun compliesFractalNoise() {
+        turbulenceNoiseShader = TurbulenceNoiseShader(useFractal = true)
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 607439b..bd67889 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -23,6 +23,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -686,7 +687,7 @@
             mListener = new InputManager.InputDeviceListener() {
                 @Override
                 public void onInputDeviceAdded(int deviceId) {
-                    final InputDevice device = InputManager.getInstance().getInputDevice(
+                    final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
                             deviceId);
                     Objects.requireNonNull(device, "Newly added input device was null.");
                     if (!device.getName().equals(deviceName)) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index e9a7f20..d94f4f2 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -194,19 +194,19 @@
 
     private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-            .setDeferUntilActive(true)
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
     /** Used for both connected/disconnected, so match using key */
     private Bundle mPowerOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED)
-            .setDeferUntilActive(true)
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
     /** Used for both low/okay, so match using key */
     private Bundle mBatteryOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY)
-            .setDeferUntilActive(true)
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
 
     private MetricsLogger mMetricsLogger;
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 19e5cb1..a3dc21e 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -320,7 +320,7 @@
                     .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
                     .setDeliveryGroupMatchingFilter(matchingFilter)
                     .setDeliveryGroupExtrasMerger(extrasMerger)
-                    .setDeferUntilActive(true)
+                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                     .toBundle();
         }
 
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c441859..409f054 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -35,7 +35,7 @@
 per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
 per-file PinnerService.java = file:/apct-tests/perftests/OWNERS
-per-file RescueParty.java = fdunlap@google.com, shuc@google.com
+per-file RescueParty.java = fdunlap@google.com, shuc@google.com, ancr@google.com, harshitmahajan@google.com
 per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
 per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file TelephonyRegistry.java = file:/telephony/OWNERS
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index d1e0f16..3de65f9 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -79,6 +79,7 @@
     static final String PROP_ATTEMPTING_FACTORY_RESET = "sys.attempting_factory_reset";
     static final String PROP_ATTEMPTING_REBOOT = "sys.attempting_reboot";
     static final String PROP_MAX_RESCUE_LEVEL_ATTEMPTED = "sys.max_rescue_level_attempted";
+    static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset";
     @VisibleForTesting
     static final int LEVEL_NONE = 0;
     @VisibleForTesting
@@ -105,10 +106,11 @@
     @VisibleForTesting
     static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
             "namespace_to_package_mapping";
+    @VisibleForTesting
+    static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
 
     private static final String NAME = "rescue-party-observer";
 
-
     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
     private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
@@ -327,8 +329,8 @@
         }
     }
 
-    private static int getMaxRescueLevel(boolean mayPerformFactoryReset) {
-        if (!mayPerformFactoryReset
+    private static int getMaxRescueLevel(boolean mayPerformReboot) {
+        if (!mayPerformReboot
                 || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
             return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
         }
@@ -339,11 +341,11 @@
      * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
      *
      * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
-     * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given
-     *                              failure.
+     * @param mayPerformReboot: whether or not a reboot and factory reset may be performed
+     *                          for the given failure.
      * @return the rescue level for the n-th mitigation attempt.
      */
-    private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
+    private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
         if (mitigationCount == 1) {
             return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
         } else if (mitigationCount == 2) {
@@ -351,9 +353,9 @@
         } else if (mitigationCount == 3) {
             return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
         } else if (mitigationCount == 4) {
-            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
+            return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
         } else if (mitigationCount >= 5) {
-            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
+            return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
         } else {
             Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
             return LEVEL_NONE;
@@ -450,6 +452,8 @@
                     break;
                 }
                 SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
+                long now = System.currentTimeMillis();
+                SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(now));
                 runnable = new Runnable() {
                     @Override
                     public void run() {
@@ -627,7 +631,7 @@
             if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
                 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                        mayPerformFactoryReset(failedPackage)));
+                        mayPerformReboot(failedPackage)));
             } else {
                 return PackageHealthObserverImpact.USER_IMPACT_NONE;
             }
@@ -642,7 +646,7 @@
             if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
                 final int level = getRescueLevel(mitigationCount,
-                        mayPerformFactoryReset(failedPackage));
+                        mayPerformReboot(failedPackage));
                 executeRescueLevel(mContext,
                         failedPackage == null ? null : failedPackage.getPackageName(), level);
                 return true;
@@ -683,8 +687,9 @@
             if (isDisabled()) {
                 return false;
             }
+            boolean mayPerformReboot = !shouldThrottleReboot();
             executeRescueLevel(mContext, /*failedPackage=*/ null,
-                    getRescueLevel(mitigationCount, true));
+                    getRescueLevel(mitigationCount, mayPerformReboot));
             return true;
         }
 
@@ -698,14 +703,27 @@
          * prompting a factory reset is an acceptable mitigation strategy for the package's
          * failure, {@code false} otherwise.
          */
-        private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) {
+        private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) {
             if (failingPackage == null) {
                 return false;
             }
+            if (shouldThrottleReboot())  {
+                return false;
+            }
 
             return isPersistentSystemApp(failingPackage.getPackageName());
         }
 
+        /**
+         * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
+         * Will return {@code false} if a factory reset was already offered recently.
+         */
+        private boolean shouldThrottleReboot() {
+            Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
+            long now = System.currentTimeMillis();
+            return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+        }
+
         private boolean isPersistentSystemApp(@NonNull String packageName) {
             PackageManager pm = mContext.getPackageManager();
             try {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c5008fa..4a0a228 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -252,12 +252,6 @@
 
     private static final boolean LOG_SERVICE_START_STOP = DEBUG_SERVICE;
 
-    // How long we wait for a service to finish executing.
-    static final int SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
-
-    // How long we wait for a service to finish executing.
-    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
-
     // Foreground service types that always get immediate notification display,
     // expressed in the same bitmask format that ServiceRecord.foregroundServiceType
     // uses.
@@ -337,6 +331,13 @@
     final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
 
     /**
+     * A global counter for generating sequence numbers to uniquely identify bindService requests.
+     * It is purely for logging purposes.
+     */
+    @GuardedBy("mAm")
+    private long mBindServiceSeqCounter = 0;
+
+    /**
      * Whether there is a rate limit that suppresses immediate re-deferral of new FGS
      * notifications from each app.  On by default, disabled only by shell command for
      * test-suite purposes.  To disable the behavior more generally, use the usual
@@ -4429,8 +4430,12 @@
             try {
                 bumpServiceExecutingLocked(r, execInFg, "bind",
                         OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                    Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+                            + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
+                }
                 r.app.getThread().scheduleBindService(r, i.intent.getIntent(), rebind,
-                        r.app.mState.getReportedProcState());
+                        r.app.mState.getReportedProcState(), mBindServiceSeqCounter++);
                 if (!rebind) {
                     i.requested = true;
                 }
@@ -6598,13 +6603,15 @@
                     return;
                 }
                 final ProcessServiceRecord psr = proc.mServices;
-                if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null) {
+                if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null
+                        || proc.isKilled()) {
                     return;
                 }
                 final long now = SystemClock.uptimeMillis();
                 final long maxTime =  now
                         - (psr.shouldExecServicesFg()
-                        ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+                        ? mAm.mConstants.SERVICE_TIMEOUT
+                        : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
                 ServiceRecord timeout = null;
                 long nextTime = 0;
                 for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
@@ -6635,8 +6642,8 @@
                             ActivityManagerService.SERVICE_TIMEOUT_MSG);
                     msg.obj = proc;
                     mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
-                            ? (nextTime + SERVICE_TIMEOUT) :
-                            (nextTime + SERVICE_BACKGROUND_TIMEOUT));
+                            ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
+                            (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT));
                 }
             }
 
@@ -6732,7 +6739,7 @@
                 ActivityManagerService.SERVICE_TIMEOUT_MSG);
         msg.obj = proc;
         mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
-                ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+                ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
     }
 
     void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5696004..4fa28a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -248,7 +248,16 @@
 
     private static final long DEFAULT_SERVICE_BIND_ALMOST_PERCEPTIBLE_TIMEOUT_MS = 15 * 1000;
 
-    // Flag stored in the DeviceConfig API.
+    /**
+     * Default value to {@link #SERVICE_TIMEOUT}.
+     */
+    private static final long DEFAULT_SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+    /**
+     * Default value to {@link #SERVICE_BACKGROUND_TIMEOUT}.
+     */
+    private static final long DEFAULT_SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT * 10;
+
     /**
      * Maximum number of cached processes.
      */
@@ -506,6 +515,12 @@
     // to restart less than this amount of time from the last one.
     public long SERVICE_MIN_RESTART_TIME_BETWEEN = DEFAULT_SERVICE_MIN_RESTART_TIME_BETWEEN;
 
+    // How long we wait for a service to finish executing.
+    long SERVICE_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+
+    // How long we wait for a service to finish executing.
+    long SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_BACKGROUND_TIMEOUT;
+
     // Maximum amount of time for there to be no activity on a service before
     // we consider it non-essential and allow its process to go on the
     // LRU background list.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c74aa7f..7550196 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14507,18 +14507,6 @@
             }
         }
 
-        // resultTo broadcasts are always infinitely deferrable.
-        if ((resultTo != null) && !ordered && mEnableModernQueue) {
-            if (brOptions == null) {
-                brOptions = BroadcastOptions.makeBasic();
-            }
-            brOptions.setDeferUntilActive(true);
-        }
-
-        if (mEnableModernQueue && ordered && brOptions != null && brOptions.isDeferUntilActive()) {
-            throw new IllegalArgumentException("Ordered broadcasts can't be deferred until active");
-        }
-
         // Verify that protected broadcasts are only being sent by system code,
         // and that system code is only sending protected broadcasts.
         final boolean isProtectedBroadcast;
@@ -19729,8 +19717,9 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(code, new AttributionSource(shellUid,
-                            "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), attributionSource.getNext()),
+                            Process.INVALID_PID, "com.android.shell",
+                            attributionSource.getAttributionTag(), attributionSource.getToken(),
+                            /*renouncedPermissions*/ null, attributionSource.getNext()),
                             shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                             skiProxyOperation);
                 } finally {
@@ -19781,8 +19770,9 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(clientId, code, new AttributionSource(shellUid,
-                            "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), attributionSource.getNext()),
+                            Process.INVALID_PID, "com.android.shell",
+                            attributionSource.getAttributionTag(), attributionSource.getToken(),
+                            /*renouncedPermissions*/ null, attributionSource.getNext()),
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                             proxiedAttributionFlags, attributionChainId);
@@ -19806,8 +19796,9 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     superImpl.apply(clientId, code, new AttributionSource(shellUid,
-                            "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), attributionSource.getNext()),
+                            Process.INVALID_PID, "com.android.shell",
+                            attributionSource.getAttributionTag(), attributionSource.getToken(),
+                            /*renouncedPermissions*/ null, attributionSource.getNext()),
                             skipProxyOperation);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f236a96..d09ca5c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -30,7 +30,6 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
-import android.app.AlarmManager;
 import android.app.StatsManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -414,18 +413,6 @@
             Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
         }
 
-        final AlarmManager am = mContext.getSystemService(AlarmManager.class);
-        mHandler.post(() -> {
-            synchronized (mStats) {
-                mStats.setLongPlugInAlarmInterface(new AlarmInterface(am, () -> {
-                    synchronized (mStats) {
-                        if (mStats.isOnBattery()) return;
-                        mStats.maybeResetWhilePluggedInLocked();
-                    }
-                }));
-            }
-        });
-
         synchronized (mPowerStatsLock) {
             mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
             if (mPowerStatsInternal != null) {
@@ -2529,32 +2516,6 @@
         }
     }
 
-    final class AlarmInterface implements BatteryStatsImpl.AlarmInterface,
-            AlarmManager.OnAlarmListener {
-        private AlarmManager mAm;
-        private Runnable mOnAlarm;
-
-        AlarmInterface(AlarmManager am, Runnable onAlarm) {
-            mAm = am;
-            mOnAlarm = onAlarm;
-        }
-
-        @Override
-        public void schedule(long rtcTimeMs, long windowLengthMs) {
-            mAm.setWindow(AlarmManager.RTC, rtcTimeMs, windowLengthMs, TAG, this, mHandler);
-        }
-
-        @Override
-        public void cancel() {
-            mAm.cancel(this);
-        }
-
-        @Override
-        public void onAlarm() {
-            mOnAlarm.run();
-        }
-    }
-
     private static native int nativeWaitWakeup(ByteBuffer outBuffer);
 
     private void dumpHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 53fcddf..33d4004 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -236,6 +236,14 @@
     private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
             ActivityManager.isLowRamDeviceStatic() ? 256 : 1024;
 
+    /**
+     * For {@link BroadcastRecord}: Default to treating all broadcasts sent by
+     * the system as be {@link BroadcastOptions#DEFERRAL_POLICY_UNTIL_ACTIVE}.
+     */
+    public boolean CORE_DEFER_UNTIL_ACTIVE = DEFAULT_CORE_DEFER_UNTIL_ACTIVE;
+    private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active";
+    private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = false;
+
     // Settings override tracking for this instance
     private String mSettingsKey;
     private SettingsObserver mSettingsObserver;
@@ -373,7 +381,12 @@
                     DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
             MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
                     DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
+            CORE_DEFER_UNTIL_ACTIVE = getDeviceConfigBoolean(KEY_CORE_DEFER_UNTIL_ACTIVE,
+                    DEFAULT_CORE_DEFER_UNTIL_ACTIVE);
         }
+
+        // TODO: migrate BroadcastRecord to accept a BroadcastConstants
+        BroadcastRecord.CORE_DEFER_UNTIL_ACTIVE = CORE_DEFER_UNTIL_ACTIVE;
     }
 
     /**
@@ -418,6 +431,8 @@
                     MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
             pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
                     MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
+            pw.print(KEY_CORE_DEFER_UNTIL_ACTIVE,
+                    CORE_DEFER_UNTIL_ACTIVE).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 59f33dd..6bd3c79 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -90,6 +90,7 @@
     final boolean prioritized; // contains more than one priority tranche
     final boolean deferUntilActive; // infinitely deferrable broadcast
     final boolean shareIdentity;  // whether the broadcaster's identity should be shared
+    final boolean urgent;    // has been classified as "urgent"
     final int userId;       // user id this broadcast was for
     final @Nullable String resolvedType; // the resolved data type
     final @Nullable String[] requiredPermissions; // permissions the caller has required
@@ -146,6 +147,13 @@
     private @Nullable String mCachedToString;
     private @Nullable String mCachedToShortString;
 
+    /**
+     * When enabled, assume that {@link UserHandle#isCore(int)} apps should
+     * treat {@link BroadcastOptions#DEFERRAL_POLICY_DEFAULT} as
+     * {@link BroadcastOptions#DEFERRAL_POLICY_UNTIL_ACTIVE}.
+     */
+    static boolean CORE_DEFER_UNTIL_ACTIVE = false;
+
     /** Empty immutable list of receivers */
     static final List<Object> EMPTY_RECEIVERS = List.of();
 
@@ -400,7 +408,9 @@
         receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
         delivery = new int[_receivers != null ? _receivers.size() : 0];
         deliveryReasons = new String[delivery.length];
-        deferUntilActive = options != null ? options.isDeferUntilActive() : false;
+        urgent = calculateUrgent(_intent, _options);
+        deferUntilActive = calculateDeferUntilActive(_callingUid,
+                _options, _resultTo, _serialized, urgent);
         deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
         blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
@@ -488,6 +498,7 @@
         pushMessageOverQuota = from.pushMessageOverQuota;
         interactive = from.interactive;
         shareIdentity = from.shareIdentity;
+        urgent = from.urgent;
         filterExtrasForReceiver = from.filterExtrasForReceiver;
     }
 
@@ -681,15 +692,8 @@
         return deferUntilActive;
     }
 
-    /**
-     * Core policy determination about this broadcast's delivery prioritization
-     */
     boolean isUrgent() {
-        // TODO: flags for controlling policy
-        // TODO: migrate alarm-prioritization flag to BroadcastConstants
-        return (isForeground()
-                || interactive
-                || alarm);
+        return urgent;
     }
 
     @NonNull String getHostingRecordTriggerType() {
@@ -849,6 +853,69 @@
         }
     }
 
+    /**
+     * Core policy determination about this broadcast's delivery prioritization
+     */
+    @VisibleForTesting
+    static boolean calculateUrgent(@NonNull Intent intent, @Nullable BroadcastOptions options) {
+        // TODO: flags for controlling policy
+        // TODO: migrate alarm-prioritization flag to BroadcastConstants
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0) {
+            return true;
+        }
+        if (options != null) {
+            if (options.isInteractive()) {
+                return true;
+            }
+            if (options.isAlarmBroadcast()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Resolve the requested {@link BroadcastOptions#setDeferralPolicy(int)}
+     * against this broadcast state to determine if it should be marked as
+     * "defer until active".
+     */
+    @VisibleForTesting
+    static boolean calculateDeferUntilActive(int callingUid, @Nullable BroadcastOptions options,
+            @Nullable IIntentReceiver resultTo, boolean ordered, boolean urgent) {
+        // Ordered broadcasts can never be deferred until active
+        if (ordered) {
+            return false;
+        }
+
+        // Unordered resultTo broadcasts are always deferred until active
+        if (!ordered && resultTo != null) {
+            return true;
+        }
+
+        // Determine if a strong preference in either direction was expressed;
+        // a preference here overrides all remaining policies
+        if (options != null) {
+            switch (options.getDeferralPolicy()) {
+                case BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE:
+                    return true;
+                case BroadcastOptions.DEFERRAL_POLICY_NONE:
+                    return false;
+            }
+        }
+
+        // Urgent broadcasts aren't deferred until active
+        if (urgent) {
+            return false;
+        }
+
+        // Otherwise, choose a reasonable default
+        if (CORE_DEFER_UNTIL_ACTIVE && UserHandle.isCore(callingUid)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index ddc9e91..844f175 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.text.TextFlags;
 import android.widget.WidgetFlags;
 
 import com.android.internal.R;
@@ -162,6 +163,11 @@
                 DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.MAGNIFIER_ASPECT_RATIO,
                 WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, float.class,
                 WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
+
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+                TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
+                TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
+                TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
         // add other device configs here...
     }
     private static volatile boolean sDeviceConfigContextEntriesLoaded = false;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index bac9253..8e93c1b 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -69,7 +69,7 @@
     // define the encoding of that data in an integer.
 
     static final int MAX_HISTORIC_STATES = 8;   // Maximum number of historic states we will keep.
-    static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
+    static final String STATE_FILE_PREFIX = "state-v2-"; // Prefix to use for state filenames.
     static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames.
     static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in.
     static long WRITE_PERIOD = 30*60*1000;      // Write file every 30 minutes or so.
@@ -462,6 +462,10 @@
             File file = files[i];
             String fileStr = file.getPath();
             if (DEBUG) Slog.d(TAG, "Collecting: " + fileStr);
+            if (!file.getName().startsWith(STATE_FILE_PREFIX)) {
+                if (DEBUG) Slog.d(TAG, "Skipping: mismatching prefix");
+                continue;
+            }
             if (!inclCheckedIn && fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX)) {
                 if (DEBUG) Slog.d(TAG, "Skipping: already checked in");
                 continue;
@@ -478,6 +482,14 @@
 
     @GuardedBy("mFileLock")
     private void trimHistoricStatesWriteLF() {
+        File[] files = mBaseDir.listFiles();
+        if (files != null) {
+            for (int i = 0; i < files.length; i++) {
+                if (!files[i].getName().startsWith(STATE_FILE_PREFIX)) {
+                    files[i].delete();
+                }
+            }
+        }
         ArrayList<String> filesArray = getCommittedFilesLF(MAX_HISTORIC_STATES, false, true);
         if (filesArray == null) {
             return;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 490a33e..e403861 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8170,7 +8170,7 @@
             volumeChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
             volumeChangedOptions.setDeliveryGroupMatchingKey(
                     AudioManager.VOLUME_CHANGED_ACTION, String.valueOf(mStreamType));
-            volumeChangedOptions.setDeferUntilActive(true);
+            volumeChangedOptions.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
             mVolumeChangedOptions = volumeChangedOptions.toBundle();
 
             mStreamDevicesChanged = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
@@ -8179,7 +8179,8 @@
             streamDevicesChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
             streamDevicesChangedOptions.setDeliveryGroupMatchingKey(
                     AudioManager.STREAM_DEVICES_CHANGED_ACTION, String.valueOf(mStreamType));
-            streamDevicesChangedOptions.setDeferUntilActive(true);
+            streamDevicesChangedOptions.setDeferralPolicy(
+                    BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
             mStreamDevicesChangedOptions = streamDevicesChangedOptions.toBundle();
         }
 
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index db6944d0..3864200 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -2088,7 +2088,7 @@
         }
 
         @VisibleForTesting
-        void onRefreshRateSettingChangedLocked(float min, float max) {
+        public void onRefreshRateSettingChangedLocked(float min, float max) {
             boolean changeable = (max - min > 1f && max > 60f);
             if (mRefreshRateChangeable != changeable) {
                 mRefreshRateChangeable = changeable;
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 3e2efdd..20ff51c 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -120,7 +120,7 @@
         options.setDeliveryGroupMatchingKey(
                 DREAMING_DELIVERY_GROUP_NAMESPACE, DREAMING_DELIVERY_GROUP_KEY);
         // This allows the broadcast delivery to be delayed to apps in the Cached state.
-        options.setDeferUntilActive(true);
+        options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         return options.toBundle();
     }
 
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 4d03e44..7e990c6 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -96,7 +96,11 @@
      */
     public abstract int getVirtualMousePointerDisplayId();
 
-    /** Gets the current position of the mouse cursor. */
+    /**
+     * Gets the current position of the mouse cursor.
+     *
+     * Returns NaN-s as the coordinates if the cursor is not available.
+     */
     public abstract PointF getCursorPosition();
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index b2b22a0..efc4f11 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2863,9 +2863,6 @@
 
         int getPointerDisplayId();
 
-        /** Gets the x and y coordinates of the cursor's current position. */
-        PointF getCursorPosition();
-
         /**
          * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event
          * occurred on a window that did not have focus.
@@ -3189,7 +3186,11 @@
 
         @Override
         public PointF getCursorPosition() {
-            return mWindowManagerCallbacks.getCursorPosition();
+            final float[] p = mNative.getMouseCursorPosition();
+            if (p == null || p.length != 2) {
+                throw new IllegalStateException("Failed to get mouse cursor position");
+            }
+            return new PointF(p[0], p[1]);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 22226e8..5395302d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -224,6 +224,16 @@
     /** Set whether stylus button reporting through motion events should be enabled. */
     void setStylusButtonMotionEventsEnabled(boolean enabled);
 
+    /**
+     * Get the current position of the mouse cursor.
+     *
+     * If the mouse cursor is not currently shown, the coordinate values will be NaN-s.
+     *
+     * NOTE: This will grab the PointerController's lock, so we must be careful about calling this
+     * from the InputReader or Display threads, which may result in a deadlock.
+     */
+    float[] getMouseCursorPosition();
+
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -465,5 +475,8 @@
 
         @Override
         public native void setStylusButtonMotionEventsEnabled(boolean enabled);
+
+        @Override
+        public native float[] getMouseCursorPosition();
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 91f91f8..e32bf1b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1488,16 +1488,19 @@
                         }
 
                         int change = isPackageDisappearing(imi.getPackageName());
-                        if (isPackageModified(imi.getPackageName())) {
-                            mAdditionalSubtypeMap.remove(imi.getId());
-                            AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
-                                    mSettings.getCurrentUserId());
-                        }
                         if (change == PACKAGE_TEMPORARY_CHANGE
                                 || change == PACKAGE_PERMANENT_CHANGE) {
                             Slog.i(TAG, "Input method uninstalled, disabling: "
                                     + imi.getComponent());
                             setInputMethodEnabledLocked(imi.getId(), false);
+                        } else if (change == PACKAGE_UPDATING) {
+                            Slog.i(TAG,
+                                    "Input method reinstalling, clearing additional subtypes: "
+                                            + imi.getComponent());
+                            mAdditionalSubtypeMap.remove(imi.getId());
+                            AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
+                                    mMethodMap,
+                                    mSettings.getCurrentUserId());
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index afae08d..2b5f874 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -421,6 +421,8 @@
     static class Injector {
 
         protected Context mContext;
+        private ServiceThread mHandlerThread;
+        private Handler mHandler;
 
         public Injector(Context context) {
             mContext = context;
@@ -431,14 +433,20 @@
         }
 
         public ServiceThread getServiceThread() {
-            ServiceThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
-                    true /*allowIo*/);
-            handlerThread.start();
-            return handlerThread;
+            if (mHandlerThread == null) {
+                mHandlerThread = new ServiceThread(TAG,
+                        Process.THREAD_PRIORITY_BACKGROUND,
+                        true /*allowIo*/);
+                mHandlerThread.start();
+            }
+            return mHandlerThread;
         }
 
         public Handler getHandler(ServiceThread handlerThread) {
-            return new Handler(handlerThread.getLooper());
+            if (mHandler == null) {
+                mHandler = new Handler(handlerThread.getLooper());
+            }
+            return mHandler;
         }
 
         public LockSettingsStorage getStorage() {
@@ -519,7 +527,8 @@
 
         public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
                 LockSettingsStorage storage) {
-            return new RebootEscrowManager(mContext, callbacks, storage);
+            return new RebootEscrowManager(mContext, callbacks, storage,
+                    getHandler(getServiceThread()));
         }
 
         public int binderGetCallingUid() {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 9b42cfc..e1cd2c5 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -205,6 +205,8 @@
 
     private final RebootEscrowKeyStoreManager mKeyStoreManager;
 
+    private final Handler mHandler;
+
     PowerManager.WakeLock mWakeLock;
 
     private ConnectivityManager.NetworkCallback mNetworkCallback;
@@ -399,19 +401,21 @@
         }
     }
 
-    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
-        this(new Injector(context, storage), callbacks, storage);
+    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage,
+            Handler handler) {
+        this(new Injector(context, storage), callbacks, storage, handler);
     }
 
     @VisibleForTesting
     RebootEscrowManager(Injector injector, Callbacks callbacks,
-            LockSettingsStorage storage) {
+            LockSettingsStorage storage, Handler handler) {
         mInjector = injector;
         mCallbacks = callbacks;
         mStorage = storage;
         mUserManager = injector.getUserManager();
         mEventLog = injector.getEventLog();
         mKeyStoreManager = injector.getKeyStoreManager();
+        mHandler = handler;
     }
 
     /** Wrapper function to set error code serialized through handler, */
@@ -937,7 +941,7 @@
 
     private void setRebootEscrowReady(boolean ready) {
         if (mRebootEscrowReady != ready) {
-            mRebootEscrowListener.onPreparedForReboot(ready);
+            mHandler.post(() -> mRebootEscrowListener.onPreparedForReboot(ready));
         }
         mRebootEscrowReady = ready;
     }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 3329f54..030c96e 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -261,11 +261,15 @@
         }
     }
 
-    private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
+    private Condition[] getValidConditions(String pkg, Condition[] conditions) {
         if (conditions == null || conditions.length == 0) return null;
         final int N = conditions.length;
         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
         for (int i = 0; i < N; i++) {
+            if (conditions[i] == null) {
+                Slog.w(TAG, "Ignoring null condition from " + pkg);
+                continue;
+            }
             final Uri id = conditions[i].id;
             if (valid.containsKey(id)) {
                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
@@ -303,7 +307,7 @@
         synchronized(mMutex) {
             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
                     + (conditions == null ? null : Arrays.asList(conditions)));
-            conditions = removeDuplicateConditions(pkg, conditions);
+            conditions = getValidConditions(pkg, conditions);
             if (conditions == null || conditions.length == 0) return;
             final int N = conditions.length;
             for (int i = 0; i < N; i++) {
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 5e0a180..8417049 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -62,7 +62,7 @@
 public class ValidateNotificationPeople implements NotificationSignalExtractor {
     // Using a shorter log tag since setprop has a limit of 32chars on variable name.
     private static final String TAG = "ValidateNoPeople";
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);;
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
@@ -105,12 +105,13 @@
     private int mEvictionCount;
     private NotificationUsageStats mUsageStats;
 
+    @Override
     public void initialize(Context context, NotificationUsageStats usageStats) {
         if (DEBUG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
         mUserToContextMap = new ArrayMap<>();
         mBaseContext = context;
         mUsageStats = usageStats;
-        mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
+        mPeopleCache = new LruCache<>(PEOPLE_CACHE_SIZE);
         mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
                 mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
         if (mEnabled) {
@@ -134,7 +135,7 @@
     // For tests: just do the setting of various local variables without actually doing work
     @VisibleForTesting
     protected void initForTests(Context context, NotificationUsageStats usageStats,
-            LruCache peopleCache) {
+            LruCache<String, LookupResult> peopleCache) {
         mUserToContextMap = new ArrayMap<>();
         mBaseContext = context;
         mUsageStats = usageStats;
@@ -142,6 +143,7 @@
         mEnabled = true;
     }
 
+    @Override
     public RankingReconsideration process(NotificationRecord record) {
         if (!mEnabled) {
             if (VERBOSE) Slog.i(TAG, "disabled");
@@ -272,7 +274,7 @@
         }
 
         if (VERBOSE) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId());
-        final LinkedList<String> pendingLookups = new LinkedList<String>();
+        final LinkedList<String> pendingLookups = new LinkedList<>();
         int personIdx = 0;
         for (String handle : people) {
             if (TextUtils.isEmpty(handle)) continue;
@@ -320,7 +322,6 @@
         return Integer.toString(userId) + ":" + handle;
     }
 
-    // VisibleForTesting
     public static String[] getExtraPeople(Bundle extras) {
         String[] peopleList = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE_LIST);
         String[] legacyPeople = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE);
@@ -417,101 +418,6 @@
         return null;
     }
 
-    private LookupResult resolvePhoneContact(Context context, final String number) {
-        Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
-                Uri.encode(number));
-        return searchContacts(context, phoneUri);
-    }
-
-    private LookupResult resolveEmailContact(Context context, final String email) {
-        Uri numberUri = Uri.withAppendedPath(
-                ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
-                Uri.encode(email));
-        return searchContacts(context, numberUri);
-    }
-
-    @VisibleForTesting
-    LookupResult searchContacts(Context context, Uri lookupUri) {
-        LookupResult lookupResult = new LookupResult();
-        final Uri corpLookupUri =
-                ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
-        if (corpLookupUri == null) {
-            addContacts(lookupResult, context, lookupUri);
-        } else {
-            addWorkContacts(lookupResult, context, corpLookupUri);
-        }
-        return lookupResult;
-    }
-
-    @VisibleForTesting
-    // Performs a contacts search using searchContacts, and then follows up by looking up
-    // any phone numbers associated with the resulting contact information and merge those
-    // into the lookup result as well. Will have no additional effect if the contact does
-    // not have any phone numbers.
-    LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
-        LookupResult lookupResult = searchContacts(context, lookupUri);
-        String phoneLookupKey = lookupResult.getPhoneLookupKey();
-        if (phoneLookupKey != null) {
-            String selection = Contacts.LOOKUP_KEY + " = ?";
-            String[] selectionArgs = new String[] { phoneLookupKey };
-            try (Cursor cursor = context.getContentResolver().query(
-                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
-                    selection, selectionArgs, /* sortOrder= */ null)) {
-                if (cursor == null) {
-                    Slog.w(TAG, "Cursor is null when querying contact phone number.");
-                    return lookupResult;
-                }
-
-                while (cursor.moveToNext()) {
-                    lookupResult.mergePhoneNumber(cursor);
-                }
-            } catch (Throwable t) {
-                Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
-            }
-        }
-        return lookupResult;
-    }
-
-    private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) {
-        final int workUserId = findWorkUserId(context);
-        if (workUserId == -1) {
-            Slog.w(TAG, "Work profile user ID not found for work contact: " + corpLookupUri);
-            return;
-        }
-        final Uri corpLookupUriWithUserId =
-                ContentProvider.maybeAddUserId(corpLookupUri, workUserId);
-        addContacts(lookupResult, context, corpLookupUriWithUserId);
-    }
-
-    /** Returns the user ID of the managed profile or -1 if none is found. */
-    private int findWorkUserId(Context context) {
-        final UserManager userManager = context.getSystemService(UserManager.class);
-        final int[] profileIds =
-                userManager.getProfileIds(context.getUserId(), /* enabledOnly= */ true);
-        for (int profileId : profileIds) {
-            if (userManager.isManagedProfile(profileId)) {
-                return profileId;
-            }
-        }
-        return -1;
-    }
-
-    /** Modifies the given lookup result to add contacts found at the given URI. */
-    private void addContacts(LookupResult lookupResult, Context context, Uri uri) {
-        try (Cursor c = context.getContentResolver().query(
-                uri, LOOKUP_PROJECTION, null, null, null)) {
-            if (c == null) {
-                Slog.w(TAG, "Null cursor from contacts query.");
-                return;
-            }
-            while (c.moveToNext()) {
-                lookupResult.mergeContact(c);
-            }
-        } catch (Throwable t) {
-            Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
-        }
-    }
-
     @VisibleForTesting
     protected static class LookupResult {
         private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
@@ -619,19 +525,18 @@
         }
     }
 
-    private class PeopleRankingReconsideration extends RankingReconsideration {
+    @VisibleForTesting
+    class PeopleRankingReconsideration extends RankingReconsideration {
         private final LinkedList<String> mPendingLookups;
         private final Context mContext;
 
-        // Amount of time to wait for a result from the contacts db before rechecking affinity.
-        private static final long LOOKUP_TIME = 1000;
         private float mContactAffinity = NONE;
         private ArraySet<String> mPhoneNumbers = null;
         private NotificationRecord mRecord;
 
         private PeopleRankingReconsideration(Context context, String key,
                 LinkedList<String> pendingLookups) {
-            super(key, LOOKUP_TIME);
+            super(key);
             mContext = context;
             mPendingLookups = pendingLookups;
         }
@@ -642,7 +547,7 @@
             long timeStartMs = System.currentTimeMillis();
             for (final String handle: mPendingLookups) {
                 final String cacheKey = getCacheKey(mContext.getUserId(), handle);
-                LookupResult lookupResult = null;
+                LookupResult lookupResult;
                 boolean cacheHit = false;
                 synchronized (mPeopleCache) {
                     lookupResult = mPeopleCache.get(cacheKey);
@@ -703,6 +608,102 @@
             }
         }
 
+        private static LookupResult resolvePhoneContact(Context context, final String number) {
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode(number));
+            return searchContacts(context, phoneUri);
+        }
+
+        private static LookupResult resolveEmailContact(Context context, final String email) {
+            Uri numberUri = Uri.withAppendedPath(
+                    ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
+                    Uri.encode(email));
+            return searchContacts(context, numberUri);
+        }
+
+        @VisibleForTesting
+        static LookupResult searchContacts(Context context, Uri lookupUri) {
+            LookupResult lookupResult = new LookupResult();
+            final Uri corpLookupUri =
+                    ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
+            if (corpLookupUri == null) {
+                addContacts(lookupResult, context, lookupUri);
+            } else {
+                addWorkContacts(lookupResult, context, corpLookupUri);
+            }
+            return lookupResult;
+        }
+
+        @VisibleForTesting
+        // Performs a contacts search using searchContacts, and then follows up by looking up
+        // any phone numbers associated with the resulting contact information and merge those
+        // into the lookup result as well. Will have no additional effect if the contact does
+        // not have any phone numbers.
+        static LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
+            LookupResult lookupResult = searchContacts(context, lookupUri);
+            String phoneLookupKey = lookupResult.getPhoneLookupKey();
+            if (phoneLookupKey != null) {
+                String selection = Contacts.LOOKUP_KEY + " = ?";
+                String[] selectionArgs = new String[] { phoneLookupKey };
+                try (Cursor cursor = context.getContentResolver().query(
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
+                        selection, selectionArgs, /* sortOrder= */ null)) {
+                    if (cursor == null) {
+                        Slog.w(TAG, "Cursor is null when querying contact phone number.");
+                        return lookupResult;
+                    }
+
+                    while (cursor.moveToNext()) {
+                        lookupResult.mergePhoneNumber(cursor);
+                    }
+                } catch (Throwable t) {
+                    Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
+                }
+            }
+            return lookupResult;
+        }
+
+        private static void addWorkContacts(LookupResult lookupResult, Context context,
+                Uri corpLookupUri) {
+            final int workUserId = findWorkUserId(context);
+            if (workUserId == -1) {
+                Slog.w(TAG, "Work profile user ID not found for work contact: " + corpLookupUri);
+                return;
+            }
+            final Uri corpLookupUriWithUserId =
+                    ContentProvider.maybeAddUserId(corpLookupUri, workUserId);
+            addContacts(lookupResult, context, corpLookupUriWithUserId);
+        }
+
+        /** Returns the user ID of the managed profile or -1 if none is found. */
+        private static int findWorkUserId(Context context) {
+            final UserManager userManager = context.getSystemService(UserManager.class);
+            final int[] profileIds =
+                    userManager.getProfileIds(context.getUserId(), /* enabledOnly= */ true);
+            for (int profileId : profileIds) {
+                if (userManager.isManagedProfile(profileId)) {
+                    return profileId;
+                }
+            }
+            return -1;
+        }
+
+        /** Modifies the given lookup result to add contacts found at the given URI. */
+        private static void addContacts(LookupResult lookupResult, Context context, Uri uri) {
+            try (Cursor c = context.getContentResolver().query(
+                    uri, LOOKUP_PROJECTION, null, null, null)) {
+                if (c == null) {
+                    Slog.w(TAG, "Null cursor from contacts query.");
+                    return;
+                }
+                while (c.moveToNext()) {
+                    lookupResult.mergeContact(c);
+                }
+            } catch (Throwable t) {
+                Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+            }
+        }
+
         @Override
         public void applyChangesLocked(NotificationRecord operand) {
             float affinityBound = operand.getContactAffinity();
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index b4d467f..721ad88 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -491,8 +491,11 @@
     public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
 
     /**
-     * Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
-     * user is not assigned to any display.
+     * Returns the main display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
+     * user is not assigned to any main display.
+     *
+     * <p>In the context of multi-user multi-display, there can be multiple main displays, at most
+     * one per each zone. Main displays are where UI is launched which a user interacts with.
      *
      * <p>The current foreground user and its running profiles are associated with the
      * {@link android.view.Display#DEFAULT_DISPLAY default display}, while other users would only be
@@ -503,9 +506,20 @@
      *
      * <p>If the user is a profile and is running, it's assigned to its parent display.
      */
+    // TODO(b/272366483) rename this method to avoid confusion with getDisplaysAssignedTOUser().
     public abstract int getDisplayAssignedToUser(@UserIdInt int userId);
 
     /**
+     * Returns all display ids assigned to the user including {@link
+     * #assignUserToExtraDisplay(int, int) extra displays}, or {@code null} if there is no display
+     * assigned to the specified user.
+     *
+     * <p>Note that this method is different from {@link #getDisplayAssignedToUser(int)}, which
+     * returns a main display only.
+     */
+    public abstract @Nullable int[] getDisplaysAssignedToUser(@UserIdInt int userId);
+
+    /**
      * Returns the main user (i.e., not a profile) that is assigned to the display, or the
      * {@link android.app.ActivityManager#getCurrentUser() current foreground user} if no user is
      * associated with the display.
@@ -573,5 +587,6 @@
 
      * @throws UserManager.CheckedUserOperationException if no switchable user can be found
      */
-    public abstract @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException;
+    public abstract @UserIdInt int getBootUser(boolean waitUntilSet)
+            throws UserManager.CheckedUserOperationException;
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3343172..fdc2aff 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -161,7 +161,9 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -278,6 +280,8 @@
     static final int WRITE_USER_MSG = 1;
     static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds
 
+    private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
+
     // Tron counters
     private static final String TRON_GUEST_CREATED = "users_guest_created";
     private static final String TRON_USER_CREATED = "users_user_created";
@@ -333,6 +337,8 @@
     /** Indicates that this is the 1st boot after the system user mode was changed by emulation. */
     private boolean mUpdatingSystemUserMode;
 
+    /** Count down latch to wait while boot user is not set.*/
+    private final CountDownLatch mBootUserLatch = new CountDownLatch(1);
     /**
      * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
      */
@@ -952,18 +958,62 @@
             Slogf.i(LOG_TAG, "setBootUser %d", userId);
             mBootUser = userId;
         }
+        mBootUserLatch.countDown();
     }
 
     @Override
     public @UserIdInt int getBootUser() {
         checkCreateUsersPermission("Get boot user");
         try {
-            return mLocalService.getBootUser();
+            return getBootUserUnchecked();
         } catch (UserManager.CheckedUserOperationException e) {
             throw e.toServiceSpecificException();
         }
     }
 
+    private @UserIdInt int getBootUserUnchecked() throws UserManager.CheckedUserOperationException {
+        synchronized (mUsersLock) {
+            if (mBootUser != UserHandle.USER_NULL) {
+                final UserData userData = mUsers.get(mBootUser);
+                if (userData != null && userData.info.supportsSwitchToByUser()) {
+                    Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
+                    return mBootUser;
+                } else {
+                    Slogf.w(LOG_TAG,
+                            "Provided boot user cannot be switched to: %d", mBootUser);
+                }
+            }
+        }
+
+        if (isHeadlessSystemUserMode()) {
+            // Return the previous foreground user, if there is one.
+            final int previousUser = getPreviousFullUserToEnterForeground();
+            if (previousUser != UserHandle.USER_NULL) {
+                Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+                return previousUser;
+            }
+            // No previous user. Return the first switchable user if there is one.
+            synchronized (mUsersLock) {
+                final int userSize = mUsers.size();
+                for (int i = 0; i < userSize; i++) {
+                    final UserData userData = mUsers.valueAt(i);
+                    if (userData.info.supportsSwitchToByUser()) {
+                        int firstSwitchable = userData.info.id;
+                        Slogf.i(LOG_TAG,
+                                "Boot user is first switchable user %d", firstSwitchable);
+                        return firstSwitchable;
+                    }
+                }
+            }
+            // No switchable users found. Uh oh!
+            throw new UserManager.CheckedUserOperationException(
+                    "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
+        }
+        // Not HSUM, return system user.
+        return UserHandle.USER_SYSTEM;
+    }
+
+
     @Override
     public int getPreviousFullUserToEnterForeground() {
         checkQueryOrCreateUsersPermission("get previous user");
@@ -1495,7 +1545,8 @@
         // intentSender
         unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
         unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        mContext.startActivity(unlockIntent);
+        mContext.startActivityAsUser(
+                unlockIntent, UserHandle.of(getProfileParentIdUnchecked(userId)));
     }
 
     @Override
@@ -5814,20 +5865,24 @@
     }
 
     /**
-     * @deprecated Use {@link
-     * android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing agents on the device with the ability to set
+     * restrictions, e.g. an Enterprise DPC and a Supervision admin. This API will only to return
+     * the restrictions set by the DPCs. To retrieve restrictions set by all agents, use
+     * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      */
-    @Deprecated
     @Override
     public Bundle getApplicationRestrictions(String packageName) {
         return getApplicationRestrictionsForUser(packageName, UserHandle.getCallingUserId());
     }
 
     /**
-     * @deprecated Use {@link
-     * android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing agents on the device with the ability to set
+     * restrictions, e.g. an Enterprise DPC and a Supervision admin. This API will only to return
+     * the restrictions set by the DPCs. To retrieve restrictions set by all agents, use
+     * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      */
-    @Deprecated
     @Override
     public Bundle getApplicationRestrictionsForUser(String packageName, @UserIdInt int userId) {
         if (UserHandle.getCallingUserId() != userId
@@ -7139,6 +7194,11 @@
         }
 
         @Override
+        public @Nullable int[] getDisplaysAssignedToUser(@UserIdInt int userId) {
+            return mUserVisibilityMediator.getDisplaysAssignedToUser(userId);
+        }
+
+        @Override
         public @UserIdInt int getUserAssignedToDisplay(int displayId) {
             return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
         }
@@ -7182,47 +7242,29 @@
         }
 
         @Override
-        public @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException {
-            synchronized (mUsersLock) {
-                // TODO(b/242195409): On Automotive, block if boot user not provided.
-                if (mBootUser != UserHandle.USER_NULL) {
-                    final UserData userData = mUsers.get(mBootUser);
-                    if (userData != null && userData.info.supportsSwitchToByUser()) {
-                        Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
-                        return mBootUser;
-                    } else {
-                        Slogf.w(LOG_TAG,
-                                "Provided boot user cannot be switched to: %d", mBootUser);
+        public @UserIdInt int getBootUser(boolean waitUntilSet)
+                throws UserManager.CheckedUserOperationException {
+            if (waitUntilSet) {
+                final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+                t.traceBegin("wait-boot-user");
+                try {
+                    if (mBootUserLatch.getCount() != 0) {
+                        Slogf.d(LOG_TAG,
+                                "Sleeping for boot user to be set. "
+                                + "Max sleep for Time: %d", BOOT_USER_SET_TIMEOUT_MS);
                     }
+                    if (!mBootUserLatch.await(BOOT_USER_SET_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                        Slogf.w(LOG_TAG, "Boot user not set. Timeout: %d",
+                                BOOT_USER_SET_TIMEOUT_MS);
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    Slogf.w(LOG_TAG, e, "InterruptedException during wait for boot user.");
                 }
+                t.traceEnd();
             }
 
-            if (isHeadlessSystemUserMode()) {
-                // Return the previous foreground user, if there is one.
-                final int previousUser = getPreviousFullUserToEnterForeground();
-                if (previousUser != UserHandle.USER_NULL) {
-                    Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
-                    return previousUser;
-                }
-                // No previous user. Return the first switchable user if there is one.
-                synchronized (mUsersLock) {
-                    final int userSize = mUsers.size();
-                    for (int i = 0; i < userSize; i++) {
-                        final UserData userData = mUsers.valueAt(i);
-                        if (userData.info.supportsSwitchToByUser()) {
-                            int firstSwitchable = userData.info.id;
-                            Slogf.i(LOG_TAG,
-                                    "Boot user is first switchable user %d", firstSwitchable);
-                            return firstSwitchable;
-                        }
-                    }
-                }
-                // No switchable users found. Uh oh!
-                throw new UserManager.CheckedUserOperationException(
-                        "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
-            }
-            // Not HSUM, return system user.
-            return UserHandle.USER_SYSTEM;
+            return getBootUserUnchecked();
         }
 
     } // class LocalService
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 12c9e98..2f99062 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -222,7 +222,7 @@
             final Set<String> userAllowlist = getInstallablePackagesForUserId(userId);
 
             pmInt.forEachPackageState(packageState -> {
-                if (packageState.getPkg() == null) {
+                if (packageState.getPkg() == null || !packageState.isSystem()) {
                     return;
                 }
                 boolean install = (userAllowlist == null
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index a8615c2..3710af6 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -63,19 +63,39 @@
 /**
  * Class responsible for deciding whether a user is visible (or visible for a given display).
  *
- * <p>Currently, it has 2 "modes" (set on constructor), which defines the class behavior (i.e, the
+ * <p>Currently, it has 3 "modes" (set on constructor), which defines the class behavior (i.e, the
  * logic that dictates the result of methods such as {@link #isUserVisible(int)} and
  * {@link #isUserVisible(int, int)}):
  *
  * <ul>
- *   <li>default: this is the most common mode (used by phones, tablets, foldables, automotives with
- *   just cluster and driver displayes, etc...), where the logic is based solely on the current
- *   foreground user (and its started profiles)
- *   <li>{@code MUMD}: mode for "(concurrent) Multiple Users on Multiple Displays", which is used on
- *   automotives with passenger display. In this mode, users started in background on the secondary
- *   display are stored in map.
+ *   <li>default (A.K.A {@code SUSD} - Single User on Single Display): this is the most common mode
+ *   (used by phones, tablets, foldables, cars with just cluster and driver displays, etc.),
+ *   where just the current foreground user and its profiles are visible; hence, most methods are
+ *   optimized to just check for the current user / profile. This mode is unit tested by
+ *   {@link com.android.server.pm.UserVisibilityMediatorSUSDTest} and CTS tested by
+ *   {@link android.multiuser.cts.UserVisibilityTest}.
+ *   <li>concurrent users (A.K.A. {@code MUMD} - Multiple Users on Multiple Displays): typically
+ *   used on automotive builds where the car has additional displays for passengers, it allows users
+ *   to be started in the background but visible on these displays; hence, it contains additional
+ *   maps to account for the visibility state. This mode is unit tested by
+ *   {@link com.android.server.pm.UserVisibilityMediatorMUMDTest} and CTS tested by
+ *   {@link android.multiuser.cts.UserVisibilityTest}.
+ *   <li>no driver (A.K.A. {@code MUPAND} - MUltiple PAssengers, No Driver): extension of the
+ *   previous mode and typically used on automotive builds where the car has additional displays for
+ *   passengers but uses a secondary Android system for the back passengers, so all "human" users
+ *   are started in the background (and the current foreground user is the system user), hence the
+ *   "no driver name". This mode is unit tested by
+ *   {@link com.android.server.pm.UserVisibilityMediatorMUPANDTest} and CTS tested by
+ *   {@link android.multiuser.cts.UserVisibilityVisibleBackgroundUsersOnDefaultDisplayTest}.
  * </ul>
  *
+ * <p>When you make changes in this class, you should run at least the 3 unit tests and
+ * {@link android.multiuser.cts.UserVisibilityTest} (which actually applies for all modes); for
+ * example, by calling {@code atest UserVisibilityMediatorSUSDTest UserVisibilityMediatorMUMDTest
+ * UserVisibilityMediatorMUPANDTest UserVisibilityTest}. Ideally, you should run the other 2 CTS
+ * tests as well (you can emulate these modes using {@code adb} commands; their javadoc provides
+ * instructions on how to do so).
+ *
  * <p>This class is thread safe.
  */
 public final class UserVisibilityMediator implements Dumpable {
@@ -786,6 +806,49 @@
         }
     }
 
+    /** See {@link UserManagerInternal#getDisplaysAssignedToUser(int)}. */
+    @Nullable
+    public int[] getDisplaysAssignedToUser(@UserIdInt int userId) {
+        int mainDisplayId = getDisplayAssignedToUser(userId);
+        if (mainDisplayId == INVALID_DISPLAY) {
+            // The user will not have any extra displays if they have no main display.
+            // Return null if no display is assigned to the user.
+            if (DBG) {
+                Slogf.d(TAG, "getDisplaysAssignedToUser(): returning null"
+                        + " because there is no display assigned to user %d", userId);
+            }
+            return null;
+        }
+
+        synchronized (mLock) {
+            if (mExtraDisplaysAssignedToUsers == null
+                    || mExtraDisplaysAssignedToUsers.size() == 0) {
+                return new int[]{mainDisplayId};
+            }
+
+            int count = 0;
+            int[] displayIds = new int[mExtraDisplaysAssignedToUsers.size() + 1];
+            displayIds[count++] = mainDisplayId;
+            for (int i = 0; i < mExtraDisplaysAssignedToUsers.size(); ++i) {
+                if (mExtraDisplaysAssignedToUsers.valueAt(i) == userId) {
+                    displayIds[count++] = mExtraDisplaysAssignedToUsers.keyAt(i);
+                }
+            }
+            // Return the array if the array length happens to be correct.
+            if (displayIds.length == count) {
+                return displayIds;
+            }
+
+            // Copy the results to a new array with the exact length. The size of displayIds[] is
+            // initialized to `1 + mExtraDisplaysAssignedToUsers.size()`, which is usually larger
+            // than the actual length, because mExtraDisplaysAssignedToUsers contains displayIds for
+            // other users. Therefore, we need to copy to a new array with the correct length.
+            int[] results = new int[count];
+            System.arraycopy(displayIds, 0, results, 0, count);
+            return results;
+        }
+    }
+
     /**
      * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
      */
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index da7aaa4..d0ed9bf 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -241,7 +241,7 @@
                 UUID.randomUUID().toString(),
                 Intent.ACTION_SCREEN_ON);
         // This allows the broadcast delivery to be delayed to apps in the Cached state.
-        options.setDeferUntilActive(true);
+        options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         return options.toBundle();
     }
 
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index bc23020..661715c 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -26,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.AlarmManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
@@ -500,14 +501,6 @@
 
     }
 
-    /** Handles calls to AlarmManager */
-    public interface AlarmInterface {
-        /** Schedule an RTC alarm */
-        void schedule(long rtcTimeMs, long windowLengthMs);
-        /** Cancel the previously scheduled alarm */
-        void cancel();
-    }
-
     private final PlatformIdleStateCallback mPlatformIdleStateCallback;
 
     private final Runnable mDeferSetCharging = new Runnable() {
@@ -1569,8 +1562,15 @@
     @GuardedBy("this")
     protected BatteryStatsConfig mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
 
-    @VisibleForTesting
-    protected AlarmInterface mLongPlugInAlarmInterface = null;
+    @GuardedBy("this")
+    private AlarmManager mAlarmManager = null;
+
+    private final AlarmManager.OnAlarmListener mLongPlugInAlarmHandler = () ->
+            mHandler.post(() -> {
+                synchronized (BatteryStatsImpl.this) {
+                    maybeResetWhilePluggedInLocked();
+                }
+            });
 
     /*
      * Holds a SamplingTimer associated with each Resource Power Manager state and voter,
@@ -11061,18 +11061,6 @@
     }
 
     /**
-     * Injects an AlarmInterface for the long plug in alarm.
-     */
-    public void setLongPlugInAlarmInterface(AlarmInterface longPlugInAlarmInterface) {
-        synchronized (this) {
-            mLongPlugInAlarmInterface = longPlugInAlarmInterface;
-            if (mBatteryPluggedIn) {
-                scheduleNextResetWhilePluggedInCheck();
-            }
-        }
-    }
-
-    /**
      * Starts tracking CPU time-in-state for threads of the system server process,
      * keeping a separate account of threads receiving incoming binder calls.
      */
@@ -14173,6 +14161,7 @@
     /**
      * Might reset battery stats if conditions are met. Assumed the device is currently plugged in.
      */
+    @VisibleForTesting
     @GuardedBy("this")
     public void maybeResetWhilePluggedInLocked() {
         final long elapsedRealtimeMs = mClock.elapsedRealtime();
@@ -14189,28 +14178,31 @@
 
     @GuardedBy("this")
     private void scheduleNextResetWhilePluggedInCheck() {
-        if (mLongPlugInAlarmInterface != null) {
-            final long timeoutMs = mClock.currentTimeMillis()
-                    + mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
-                    * DateUtils.HOUR_IN_MILLIS;
-            Calendar nextAlarm = Calendar.getInstance();
-            nextAlarm.setTimeInMillis(timeoutMs);
+        if (mAlarmManager == null) return;
+        final long timeoutMs = mClock.currentTimeMillis()
+                + mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
+                * DateUtils.HOUR_IN_MILLIS;
+        Calendar nextAlarm = Calendar.getInstance();
+        nextAlarm.setTimeInMillis(timeoutMs);
 
-            // Find the 2 AM the same day as the end of the minimum duration.
-            // This logic does not handle a Daylight Savings transition, or a timezone change
-            // while the alarm has been set. The need to reset after a long period while plugged
-            // in is not strict enough to warrant a well architected out solution.
-            nextAlarm.set(Calendar.MILLISECOND, 0);
-            nextAlarm.set(Calendar.SECOND, 0);
-            nextAlarm.set(Calendar.MINUTE, 0);
-            nextAlarm.set(Calendar.HOUR_OF_DAY, 2);
-            long nextTimeMs = nextAlarm.getTimeInMillis();
-            if (nextTimeMs < timeoutMs) {
-                // The 2AM on the day of the timeout, move on the next day.
-                nextTimeMs += DateUtils.DAY_IN_MILLIS;
-            }
-            mLongPlugInAlarmInterface.schedule(nextTimeMs, DateUtils.HOUR_IN_MILLIS);
+        // Find the 2 AM the same day as the end of the minimum duration.
+        // This logic does not handle a Daylight Savings transition, or a timezone change
+        // while the alarm has been set. The need to reset after a long period while plugged
+        // in is not strict enough to warrant a well architected out solution.
+        nextAlarm.set(Calendar.MILLISECOND, 0);
+        nextAlarm.set(Calendar.SECOND, 0);
+        nextAlarm.set(Calendar.MINUTE, 0);
+        nextAlarm.set(Calendar.HOUR_OF_DAY, 2);
+        long possibleNextTimeMs = nextAlarm.getTimeInMillis();
+        if (possibleNextTimeMs < timeoutMs) {
+            // The 2AM on the day of the timeout, move on the next day.
+            possibleNextTimeMs += DateUtils.DAY_IN_MILLIS;
         }
+        final long nextTimeMs = possibleNextTimeMs;
+        final AlarmManager am = mAlarmManager;
+        mHandler.post(() -> am.setWindow(AlarmManager.RTC, nextTimeMs,
+                DateUtils.HOUR_IN_MILLIS,
+                TAG, mLongPlugInAlarmHandler, mHandler));
     }
 
 
@@ -14339,8 +14331,12 @@
                 initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
             }
             mBatteryPluggedIn = false;
-            if (mLongPlugInAlarmInterface != null) {
-                mLongPlugInAlarmInterface.cancel();
+            if (mAlarmManager != null) {
+                final AlarmManager am = mAlarmManager;
+                mHandler.post(() -> {
+                    // No longer plugged in. Cancel the long plug in alarm.
+                    am.cancel(mLongPlugInAlarmHandler);
+                });
             }
             mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
             mDischargeCurrentLevel = mDischargeUnplugLevel = level;
@@ -15178,6 +15174,14 @@
     public void systemServicesReady(Context context) {
         mConstants.startObserving(context.getContentResolver());
         registerUsbStateReceiver(context);
+
+        synchronized (this) {
+            mAlarmManager = context.getSystemService(AlarmManager.class);
+            if (mBatteryPluggedIn) {
+                // Already plugged in. Schedule the long plug in alarm.
+                scheduleNextResetWhilePluggedInCheck();
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 8e8abf6..96f4a01 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -250,15 +250,7 @@
                 service.checkRecognitionSupport(recognizerIntent, attributionSource, callback));
     }
 
-    void triggerModelDownload(Intent recognizerIntent, AttributionSource attributionSource) {
-        if (!mConnected) {
-            Slog.e(TAG, "#downloadModel failed due to connection.");
-            return;
-        }
-        run(service -> service.triggerModelDownload(recognizerIntent, attributionSource));
-    }
-
-    void setModelDownloadListener(
+    void triggerModelDownload(
             Intent recognizerIntent,
             AttributionSource attributionSource,
             IModelDownloadListener listener) {
@@ -266,25 +258,12 @@
             try {
                 listener.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to report the connection broke to the caller.", e);
+                Slog.w(TAG, "#downloadModel failed due to connection.", e);
                 e.printStackTrace();
             }
             return;
         }
-
-        run(service ->
-                service.setModelDownloadListener(recognizerIntent, attributionSource, listener));
-    }
-
-    void clearModelDownloadListener(
-            Intent recognizerIntent,
-            AttributionSource attributionSource) {
-        if (!mConnected) {
-            return;
-        }
-
-        run(service ->
-                service.clearModelDownloadListener(recognizerIntent, attributionSource));
+        run(service -> service.triggerModelDownload(recognizerIntent, attributionSource, listener));
     }
 
     void shutdown(IBinder clientToken) {
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bc73db1..bff6d50 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -193,25 +193,11 @@
                         @Override
                         public void triggerModelDownload(
                                 Intent recognizerIntent,
-                                AttributionSource attributionSource) {
-                            service.triggerModelDownload(recognizerIntent, attributionSource);
-                        }
-
-                        @Override
-                        public void setModelDownloadListener(
-                                Intent recognizerIntent,
                                 AttributionSource attributionSource,
-                                IModelDownloadListener listener) throws RemoteException {
-                            service.setModelDownloadListener(
+                                IModelDownloadListener listener) {
+                            service.triggerModelDownload(
                                     recognizerIntent, attributionSource, listener);
                         }
-
-                        @Override
-                        public void clearModelDownloadListener(
-                                Intent recognizerIntent,
-                                AttributionSource attributionSource) throws RemoteException {
-                            service.clearModelDownloadListener(recognizerIntent, attributionSource);
-                        }
                     });
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error creating a speech recognition session", e);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index ed91775..2d3928c 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1019,6 +1019,7 @@
         int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         // If the desired frontend id was specified, we only need to check the frontend.
         boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
         for (FrontendResource fr : getFrontendResources().values()) {
@@ -1048,6 +1049,8 @@
                     if (currentLowestPriority > priority) {
                         inUseLowestPriorityFrHandle = fr.getHandle();
                         currentLowestPriority = priority;
+                        isRequestFromSameProcess = (requestClient.getProcessId()
+                            == (getClientProfile(fr.getOwnerClientId())).getProcessId());
                     }
                 }
             }
@@ -1063,7 +1066,8 @@
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
-                && (requestClient.getPriority() > currentLowestPriority)) {
+            && ((requestClient.getPriority() > currentLowestPriority) || (
+            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(
                     getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
@@ -1182,6 +1186,7 @@
         int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         for (LnbResource lnb : getLnbResources().values()) {
             if (!lnb.isInUse()) {
                 // Grant the unused lnb with lower handle first
@@ -1194,6 +1199,8 @@
                 if (currentLowestPriority > priority) {
                     inUseLowestPriorityLnbHandle = lnb.getHandle();
                     currentLowestPriority = priority;
+                    isRequestFromSameProcess = (requestClient.getProcessId()
+                        == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
                 }
             }
         }
@@ -1208,7 +1215,8 @@
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
-                && (requestClient.getPriority() > currentLowestPriority)) {
+            && ((requestClient.getPriority() > currentLowestPriority) || (
+            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
                     TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
                 return false;
@@ -1240,6 +1248,7 @@
         int lowestPriorityOwnerId = -1;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         if (!cas.isFullyUsed()) {
             casSessionHandle[0] = generateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
@@ -1252,12 +1261,15 @@
             if (currentLowestPriority > priority) {
                 lowestPriorityOwnerId = ownerId;
                 currentLowestPriority = priority;
+                isRequestFromSameProcess = (requestClient.getProcessId()
+                    == (getClientProfile(ownerId)).getProcessId());
             }
         }
 
         // When all the Cas sessions are occupied, reclaim the lowest priority client if the
         // request client has higher priority.
-        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+        if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
+        || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(lowestPriorityOwnerId,
                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
                 return false;
@@ -1289,6 +1301,7 @@
         int lowestPriorityOwnerId = -1;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         if (!ciCam.isFullyUsed()) {
             ciCamHandle[0] = generateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
@@ -1301,12 +1314,16 @@
             if (currentLowestPriority > priority) {
                 lowestPriorityOwnerId = ownerId;
                 currentLowestPriority = priority;
+                isRequestFromSameProcess = (requestClient.getProcessId()
+                    == (getClientProfile(ownerId)).getProcessId());
             }
         }
 
         // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
         // request client has higher priority.
-        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+        if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
+            || ((requestClient.getPriority() == currentLowestPriority)
+                && isRequestFromSameProcess))) {
             if (!reclaimResource(lowestPriorityOwnerId,
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
                 return false;
@@ -1424,6 +1441,7 @@
         int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         // If the desired demux id was specified, we only need to check the demux.
         boolean hasDesiredDemuxCap = request.desiredFilterTypes
                 != DemuxFilterMainType.UNDEFINED;
@@ -1448,6 +1466,8 @@
                         // update lowest priority
                         if (currentLowestPriority > priority) {
                             currentLowestPriority = priority;
+                            isRequestFromSameProcess = (requestClient.getProcessId()
+                                == (getClientProfile(dr.getOwnerClientId())).getProcessId());
                             shouldUpdate = true;
                         }
                         // update smallest caps
@@ -1473,7 +1493,8 @@
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
-                && (requestClient.getPriority() > currentLowestPriority)) {
+            && ((requestClient.getPriority() > currentLowestPriority) || (
+            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(
                     getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
                     TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d108f0d..f14a432 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -251,11 +251,6 @@
                     // {@link #restartActivityProcessIfVisible}.
                     restartingName = r.app.mName;
                     restartingUid = r.app.mUid;
-                    // Make EnsureActivitiesVisibleHelper#makeVisibleAndRestartIfNeeded not skip
-                    // restarting non-top activity.
-                    if (r != r.getTask().topRunningActivity()) {
-                        r.setVisibleRequested(false);
-                    }
                 }
                 r.activityStopped(icicle, persistentState, description);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d15d094..324a0ad 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4135,9 +4135,7 @@
         } else if (!mVisibleRequested && launchCount > 2
                 && lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
             // We have launched this activity too many times since it was able to run, so give up
-            // and remove it. (Note if the activity is visible, we don't remove the record. We leave
-            // the dead window on the screen but the process will not be restarted unless user
-            // explicitly tap on it.)
+            // and remove it.
             remove = true;
         } else {
             // The process may be gone, but the activity lives on!
@@ -4159,11 +4157,6 @@
             if (DEBUG_APP) {
                 Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this);
             }
-            // Set nowVisible to previous visible state. If the app was visible while it died, we
-            // leave the dead window on screen so it's basically visible. This is needed when user
-            // later tap on the dead window, we need to stop other apps when user transfers focus
-            // to the restarted activity.
-            nowVisible = mVisibleRequested;
         }
         // upgrade transition trigger to task if this is the last activity since it means we are
         // closing the task.
@@ -5232,6 +5225,11 @@
             Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
             return;
         }
+        if (visible == mVisibleRequested && visible == mVisible
+                && mTransitionController.isShellTransitionsEnabled()) {
+            // For shell transition, it is no-op if there is no state change.
+            return;
+        }
         if (visible) {
             mDeferHidingClient = false;
         }
@@ -5270,13 +5268,18 @@
 
         // Before setting mVisibleRequested so we can track changes.
         boolean isCollecting = false;
+        boolean inFinishingTransition = false;
         if (mTransitionController.isShellTransitionsEnabled()) {
             isCollecting = mTransitionController.isCollecting();
             if (isCollecting) {
                 mTransitionController.collect(this);
             } else {
-                Slog.e(TAG, "setVisibility=" + visible + " while transition is not collecting "
-                        + this + " caller=" + Debug.getCallers(8));
+                inFinishingTransition = mTransitionController.inFinishingTransition(this);
+                if (!inFinishingTransition) {
+                    Slog.e(TAG, "setVisibility=" + visible
+                            + " while transition is not collecting or finishing "
+                            + this + " caller=" + Debug.getCallers(8));
+                }
             }
         }
 
@@ -5290,10 +5293,6 @@
         mLastDeferHidingClient = deferHidingClient;
 
         if (!visible) {
-            // If the app is dead while it was visible, we kept its dead window on screen.
-            // Now that the app is going invisible, we can remove it. It will be restarted
-            // if made visible again.
-            removeDeadWindows();
             // If this activity is about to finish/stopped and now becomes invisible, remove it
             // from the unknownApp list in case the activity does not want to draw anything, which
             // keep the user waiting for the next transition to start.
@@ -5357,6 +5356,10 @@
             }
             return;
         }
+        if (inFinishingTransition) {
+            // Let the finishing transition commit the visibility.
+            return;
+        }
         // If we are preparing an app transition, then delay changing
         // the visibility of this token until we execute that transition.
         if (deferCommitVisibilityChange(visible)) {
@@ -6642,9 +6645,6 @@
         // stop tracking
         mSplashScreenStyleSolidColor = true;
 
-        // We now have a good window to show, remove dead placeholders
-        removeDeadWindows();
-
         if (mStartingWindow != null) {
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
                     + ": first real window is shown, no animation", win.mToken);
@@ -7380,20 +7380,6 @@
         }
     }
 
-    void removeDeadWindows() {
-        for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
-            WindowState win = mChildren.get(winNdx);
-            if (win.mAppDied) {
-                ProtoLog.w(WM_DEBUG_ADD_REMOVE,
-                        "removeDeadWindows: %s", win);
-                // Set mDestroying, we don't want any animation or delayed removal here.
-                win.mDestroying = true;
-                // Also removes child windows.
-                win.removeIfPossible();
-            }
-        }
-    }
-
     void setWillReplaceWindows(boolean animate) {
         ProtoLog.d(WM_DEBUG_ADD_REMOVE,
                 "Marking app token %s with replacing windows.", this);
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 5d038dc..be7d9b6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -92,6 +92,7 @@
         } else {
             mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE);
         }
+        mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId());
         return mInputWindowHandleWrapper;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 211c230..ce29564 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1680,6 +1680,11 @@
                 targetTask.removeImmediately("bulky-task");
                 return START_ABORTED;
             }
+            // When running transient transition, the transient launch target should keep on top.
+            // So disallow the transient hide activity to move itself to front, e.g. trampoline.
+            if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
+                mAvoidMoveToFront = true;
+            }
             mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
         }
 
@@ -1796,7 +1801,7 @@
                 // root-task to the will not update the focused root-task.  If starting the new
                 // activity now allows the task root-task to be focusable, then ensure that we
                 // now update the focused root-task accordingly.
-                if (mTargetRootTask.isTopActivityFocusable()
+                if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
                         && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
                     mTargetRootTask.moveToFront("startActivityInner");
                 }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f9f972c..b67bc62 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -862,8 +862,16 @@
                 WindowContainer target, boolean isOpen) {
             final BackWindowAnimationAdaptor adaptor =
                     new BackWindowAnimationAdaptor(target, isOpen);
-            target.startAnimation(target.getPendingTransaction(), adaptor, false /* hidden */,
-                    ANIMATION_TYPE_PREDICT_BACK);
+            final SurfaceControl.Transaction pt = target.getPendingTransaction();
+            target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
+            // Workaround to show TaskFragment which can be hide in Transitions and won't show
+            // during isAnimating.
+            if (isOpen && target.asActivityRecord() != null) {
+                final TaskFragment fragment = target.asActivityRecord().getTaskFragment();
+                if (fragment != null) {
+                    pt.show(fragment.mSurfaceControl);
+                }
+            }
             return adaptor;
         }
 
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index dde89e9..9cc311d 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -193,7 +193,7 @@
             }
 
             if (!r.attachedToProcess()) {
-                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
+                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
                         resumeTopActivity && isTop, r);
             } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
@@ -243,15 +243,7 @@
     }
 
     private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
-            boolean isTop, boolean andResume, ActivityRecord r) {
-        // We need to make sure the app is running if it's the top, or it is just made visible from
-        // invisible. If the app is already visible, it must have died while it was visible. In this
-        // case, we'll show the dead window but will not restart the app. Otherwise we could end up
-        // thrashing.
-        if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
-            return;
-        }
-
+            boolean andResume, ActivityRecord r) {
         // This activity needs to be visible, but isn't even running...
         // get it started and resume if no other root task in this root task is resumed.
         if (DEBUG_VISIBILITY) {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1e9d451..0a47fe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,7 +25,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.graphics.PointF;
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
@@ -221,11 +220,6 @@
     }
 
     @Override
-    public PointF getCursorPosition() {
-        return mService.getLatestMousePosition();
-    }
-
-    @Override
     public void onPointerDownOutsideFocus(IBinder touchedToken) {
         mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
     }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f355f08..5db39fc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -997,7 +997,8 @@
 
     @VisibleForTesting
     boolean shouldShowLetterboxUi(WindowState mainWindow) {
-        return isSurfaceVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
+        return (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+                && mainWindow.areAppWindowBoundsLetterboxed()
                 // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
                 // WindowContainer#showWallpaper because the later will return true when this
                 // activity is using blurred wallpaper for letterbox background.
@@ -1104,7 +1105,7 @@
     // for all corners for consistency and pick a minimal bottom one for consistency with a
     // taskbar rounded corners.
     int getRoundedCornersRadius(final WindowState mainWindow) {
-        if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+        if (!requiresRoundedCorners(mainWindow)) {
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 969f65c..67ca844 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3398,8 +3398,10 @@
 
         final boolean isTopActivityResumed = top != null
                 && top.getOrganizedTask() == this && top.isState(RESUMED);
-        // Whether the direct top activity is in size compat mode on foreground.
-        info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode();
+        final boolean isTopActivityVisible = top != null
+                && top.getOrganizedTask() == this && top.isVisible();
+        // Whether the direct top activity is in size compat mode
+        info.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode();
         if (info.topActivityInSizeCompat
                 && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
             // We hide the restart button in case of transparent activities.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2ddb307..7c57dc1 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1012,6 +1012,10 @@
         if (isTopActivityLaunchedBehind()) {
             return TASK_FRAGMENT_VISIBILITY_VISIBLE;
         }
+        final Task thisTask = asTask();
+        if (thisTask != null && mTransitionController.isTransientHide(thisTask)) {
+            return TASK_FRAGMENT_VISIBILITY_VISIBLE;
+        }
 
         boolean gotTranslucentFullscreen = false;
         boolean gotTranslucentAdjacent = false;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 606f011..97f48b7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -194,6 +194,13 @@
      */
     private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
 
+    /**
+     * The tasks that may be occluded by the transient activity. Assume the task stack is
+     * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
+     * task, and [B, C] are the transient-hide tasks.
+     */
+    private ArrayList<Task> mTransientHideTasks;
+
     /** Custom activity-level animation options and callbacks. */
     private TransitionInfo.AnimationOptions mOverrideOptions;
     private IRemoteCallback mClientAnimationStartCallback = null;
@@ -265,35 +272,51 @@
     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
         if (mTransientLaunches == null) {
             mTransientLaunches = new ArrayMap<>();
+            mTransientHideTasks = new ArrayList<>();
         }
         mTransientLaunches.put(activity, restoreBelow);
         setTransientLaunchToChanges(activity);
 
         if (restoreBelow != null) {
-            final ChangeInfo info = mChanges.get(restoreBelow);
-            if (info != null) {
-                info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+            // Collect all visible activities which can be occluded by the transient activity to
+            // make sure they are in the participants so their visibilities can be updated when
+            // finishing transition.
+            ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
+                if (t.isVisibleRequested() && !t.isAlwaysOnTop()
+                        && !t.getWindowConfiguration().tasksAreFloating()) {
+                    if (t.isRootTask()) {
+                        mTransientHideTasks.add(t);
+                    }
+                    if (t.isLeafTask()) {
+                        t.forAllActivities(r -> {
+                            if (r.isVisibleRequested()) {
+                                collect(r);
+                            }
+                        });
+                    }
+                }
+                return t == restoreBelow;
+            });
+            // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
+            // so ChangeInfo#hasChanged() can return true to report the transition info.
+            for (int i = mChanges.size() - 1; i >= 0; --i) {
+                final WindowContainer<?> wc = mChanges.keyAt(i);
+                if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue;
+                if (isInTransientHide(wc)) {
+                    mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+                }
             }
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
                 + "transient-launch", mSyncId, activity);
     }
 
-    boolean isTransientHide(@NonNull Task task) {
-        if (mTransientLaunches == null) return false;
-        for (int i = 0; i < mTransientLaunches.size(); ++i) {
-            if (mTransientLaunches.valueAt(i) == task) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /** @return whether `wc` is a descendent of a transient-hide window. */
     boolean isInTransientHide(@NonNull WindowContainer wc) {
-        if (mTransientLaunches == null) return false;
-        for (int i = 0; i < mTransientLaunches.size(); ++i) {
-            if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) {
+        if (mTransientHideTasks == null) return false;
+        for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) {
+            final Task task = mTransientHideTasks.get(i);
+            if (wc == task || wc.isDescendantOf(task)) {
                 return true;
             }
         }
@@ -675,7 +698,7 @@
      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
      * Additionally, this gives shell the ability to better deal with merged transitions.
      */
-    private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) {
+    private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
         final Point tmpPos = new Point();
         // usually only size 1
         final ArraySet<DisplayContent> displays = new ArraySet<>();
@@ -728,8 +751,8 @@
         } finally {
             mController.mBuildingFinishLayers = false;
         }
-        if (rootLeash.isValid()) {
-            t.reparent(rootLeash, null);
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            t.reparent(info.getRoot(i).getLeash(), null);
         }
     }
 
@@ -814,6 +837,15 @@
         if (mState < STATE_PLAYING) {
             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
         }
+        mController.mFinishingTransition = this;
+
+        if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+            // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
+            // the update to make the activities in the tasks invisible-requested, then the next
+            // step can continue to commit the visibility.
+            mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
+                    0 /* configChanges */, true /* preserveWindows */);
+        }
 
         boolean hasParticipatedDisplay = false;
         boolean hasVisibleTransientLaunch = false;
@@ -995,6 +1027,7 @@
 
         // Handle back animation if it's already started.
         mController.mAtm.mBackNavigationController.handleDeferredBackAnimation(mTargets);
+        mController.mFinishingTransition = null;
     }
 
     void abort() {
@@ -1066,13 +1099,6 @@
             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
             return;
         }
-        if (mTargetDisplays.isEmpty()) {
-            mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay());
-        }
-        // While there can be multiple DC's involved. For now, we just use the first one as
-        // the "primary" one for most things. Eventually, this will need to change, but, for the
-        // time being, we don't have full cross-display transitions so it isn't a problem.
-        final DisplayContent dc = mTargetDisplays.get(0);
 
         // Commit the visibility of visible activities before calculateTransitionInfo(), so the
         // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
@@ -1082,6 +1108,9 @@
 
         if (mState == STATE_ABORT) {
             mController.abort(this);
+            // Fall-back to the default display if there isn't one participating.
+            final DisplayContent dc = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
+                    : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
             dc.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
@@ -1094,16 +1123,24 @@
         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
         mController.moveToPlaying(this);
 
-        if (dc.isKeyguardLocked()) {
-            mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
-        }
-
         // Check whether the participants were animated from back navigation.
         final boolean markBackAnimated = mController.mAtm.mBackNavigationController
                 .containsBackAnimationTargets(this);
-        // Resolve the animating targets from the participants
+        // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
+
+        // Repopulate the displays based on the resolved targets.
+        mTargetDisplays.clear();
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
+                    info.getRoot(i).getDisplayId());
+            mTargetDisplays.add(dc);
+            if (dc.isKeyguardLocked()) {
+                mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
+            }
+        }
+
         if (markBackAnimated) {
             mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
         }
@@ -1125,9 +1162,12 @@
         }
 
         // TODO(b/188669821): Move to animation impl in shell.
-        handleLegacyRecentsStartBehavior(dc, info);
+        for (int i = 0; i < mTargetDisplays.size(); ++i) {
+            handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info);
+            if (mRecentsDisplayId != INVALID_DISPLAY) break;
+        }
 
-        handleNonAppWindowsInTransition(dc, mType, mFlags);
+        handleNonAppWindowsInTransition(mType, mFlags);
 
         // The callback is only populated for custom activity-level client animations
         sendRemoteCallback(mClientAnimationStartCallback);
@@ -1166,14 +1206,13 @@
 
         // Record windowtokens (activity/wallpaper) that are expected to be visible after the
         // transition animation. This will be used in finishTransition to prevent prematurely
-        // committing visibility.
-        for (int i = mParticipants.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mParticipants.valueAt(i);
-            if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
-            // don't include transient launches, though, since those are only temporarily visible.
-            if (mTransientLaunches != null && wc.asActivityRecord() != null
-                    && mTransientLaunches.containsKey(wc.asActivityRecord())) continue;
-            mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
+        // committing visibility. Skip transient launches since those are only temporarily visible.
+        if (mTransientLaunches == null) {
+            for (int i = mParticipants.size() - 1; i >= 0; --i) {
+                final WindowContainer wc = mParticipants.valueAt(i);
+                if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
+                mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
+            }
         }
 
         // Take task snapshots before the animation so that we can capture IME before it gets
@@ -1191,11 +1230,14 @@
 
         // This is non-null only if display has changes. It handles the visible windows that don't
         // need to be participated in the transition.
-        final AsyncRotationController controller = dc.getAsyncRotationController();
-        if (controller != null && containsChangeFor(dc, mTargets)) {
-            controller.setupStartTransaction(transaction);
+        for (int i = 0; i < mTargetDisplays.size(); ++i) {
+            final DisplayContent dc = mTargetDisplays.get(i);
+            final AsyncRotationController controller = dc.getAsyncRotationController();
+            if (controller != null && containsChangeFor(dc, mTargets)) {
+                controller.setupStartTransaction(transaction);
+            }
         }
-        buildFinishTransaction(mFinishTransaction, info.getRootLeash());
+        buildFinishTransaction(mFinishTransaction, info);
         if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
             mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
             try {
@@ -1216,10 +1258,13 @@
                 // client, we should finish and apply it here so the transactions aren't lost.
                 postCleanupOnFailure();
             }
-            final AccessibilityController accessibilityController =
-                    dc.mWmService.mAccessibilityController;
-            if (accessibilityController.hasCallbacks()) {
-                accessibilityController.onWMTransition(dc.getDisplayId(), mType);
+            for (int i = 0; i < mTargetDisplays.size(); ++i) {
+                final DisplayContent dc = mTargetDisplays.get(i);
+                final AccessibilityController accessibilityController =
+                        dc.mWmService.mAccessibilityController;
+                if (accessibilityController.hasCallbacks()) {
+                    accessibilityController.onWMTransition(dc.getDisplayId(), mType);
+                }
             }
         } else {
             // No player registered or it's not enabled, so just finish/apply immediately
@@ -1261,7 +1306,7 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(mToken);
+        mController.finishTransition(this);
     }
 
     private void cleanUpInternal() {
@@ -1296,16 +1341,15 @@
         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
             return;
         }
-        mRecentsDisplayId = dc.mDisplayId;
 
         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
         final InputConsumerImpl recentsAnimationInputConsumer =
                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
+        ActivityRecord recentsActivity = null;
         if (recentsAnimationInputConsumer != null) {
             // find the top-most going-away activity and the recents activity. The top-most
             // is used as layer reference while the recents is used for registering the consumer
             // override.
-            ActivityRecord recentsActivity = null;
             ActivityRecord topActivity = null;
             for (int i = 0; i < info.getChanges().size(); ++i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
@@ -1329,6 +1373,12 @@
             }
         }
 
+        if (recentsActivity == null) {
+            // No recents activity on `dc`, its probably on a different display.
+            return;
+        }
+        mRecentsDisplayId = dc.mDisplayId;
+
         // The rest of this function handles nav-bar reparenting
 
         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
@@ -1423,7 +1473,7 @@
         }
     }
 
-    private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
+    private void handleNonAppWindowsInTransition(
             @TransitionType int transit, @TransitionFlags int flags) {
         if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
             // If the occlusion changed but the transition isn't an occlude/unocclude transition,
@@ -1801,6 +1851,41 @@
         return wc instanceof DisplayContent;
     }
 
+    private static int getDisplayId(@NonNull WindowContainer wc) {
+        return wc.getDisplayContent() != null
+                ? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY;
+    }
+
+    @VisibleForTesting
+    static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
+            ArrayList<ChangeInfo> sortedTargets,
+            @NonNull SurfaceControl.Transaction startT) {
+        // There needs to be a root on each display.
+        for (int i = 0; i < sortedTargets.size(); ++i) {
+            final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
+            // Don't include wallpapers since they are in a different DA.
+            if (isWallpaper(wc)) continue;
+            final int endDisplayId = getDisplayId(wc);
+            if (endDisplayId < 0) continue;
+
+            // Check if Root was already created for this display with a higher-Z window
+            if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
+
+            WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
+
+            // Make leash based on highest (z-order) direct child of ancestor with a participant.
+            WindowContainer leashReference = wc;
+            while (leashReference.getParent() != ancestor) {
+                leashReference = leashReference.getParent();
+            }
+            final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
+                    "Transition Root: " + leashReference.getName()).build();
+            startT.setLayer(rootLeash, leashReference.getLastLayer());
+            outInfo.addRootLeash(endDisplayId, rootLeash,
+                    ancestor.getBounds().left, ancestor.getBounds().top);
+        }
+    }
+
     /**
      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
      * root surface.
@@ -1811,37 +1896,13 @@
     @NonNull
     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
             ArrayList<ChangeInfo> sortedTargets,
-            @Nullable SurfaceControl.Transaction startT) {
+            @NonNull SurfaceControl.Transaction startT) {
         final TransitionInfo out = new TransitionInfo(type, flags);
-
-        WindowContainer<?> topApp = null;
-        for (int i = 0; i < sortedTargets.size(); i++) {
-            final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
-            if (!isWallpaper(wc)) {
-                topApp = wc;
-                break;
-            }
-        }
-        if (topApp == null) {
-            out.setRootLeash(new SurfaceControl(), 0, 0);
+        calculateTransitionRoots(out, sortedTargets, startT);
+        if (out.getRootCount() == 0) {
             return out;
         }
 
-        WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, topApp);
-
-        // Make leash based on highest (z-order) direct child of ancestor with a participant.
-        // TODO(b/261418859): Handle the case when the target contains window containers which
-        // belong to a different display. As a workaround we use topApp, from which wallpaper
-        // window container is removed, instead of sortedTargets here.
-        WindowContainer leashReference = topApp;
-        while (leashReference.getParent() != ancestor) {
-            leashReference = leashReference.getParent();
-        }
-        final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
-                "Transition Root: " + leashReference.getName()).build();
-        startT.setLayer(rootLeash, leashReference.getLastLayer());
-        out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
-
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
         final int count = sortedTargets.size();
         for (int i = 0; i < count; ++i) {
@@ -1861,6 +1922,7 @@
             change.setMode(info.getTransitMode(target));
             change.setStartAbsBounds(info.mAbsoluteBounds);
             change.setFlags(info.getChangeFlags(target));
+            change.setDisplayId(info.mDisplayId, getDisplayId(target));
 
             final Task task = target.asTask();
             final TaskFragment taskFragment = target.asTaskFragment();
@@ -1947,6 +2009,15 @@
         }
 
         TransitionInfo.AnimationOptions animOptions = null;
+
+        // Check if the top-most app is an activity (ie. activity->activity). If so, make sure to
+        // honor its custom transition options.
+        WindowContainer<?> topApp = null;
+        for (int i = 0; i < sortedTargets.size(); i++) {
+            if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
+            topApp = sortedTargets.get(i).mContainer;
+            break;
+        }
         if (topApp.asActivityRecord() != null) {
             final ActivityRecord topActivity = topApp.asActivityRecord();
             animOptions = addCustomActivityTransition(topActivity, true/* open */, null);
@@ -1997,14 +2068,15 @@
     private static WindowContainer<?> findCommonAncestor(
             @NonNull ArrayList<ChangeInfo> targets,
             @NonNull WindowContainer<?> topApp) {
+        final int displayId = getDisplayId(topApp);
         WindowContainer<?> ancestor = topApp.getParent();
         // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
         // null because all targets are attached.
         for (int i = targets.size() - 1; i >= 0; i--) {
             final ChangeInfo change = targets.get(i);
             final WindowContainer wc = change.mContainer;
-            if (isWallpaper(wc)) {
-                // Skip the non-app window.
+            if (isWallpaper(wc) || getDisplayId(wc) != displayId) {
+                // Skip the non-app window or windows on a different display
                 continue;
             }
             while (!wc.isDescendantOf(ancestor)) {
@@ -2180,6 +2252,7 @@
         final Rect mAbsoluteBounds = new Rect();
         boolean mShowWallpaper;
         int mRotation = ROTATION_UNDEFINED;
+        int mDisplayId = -1;
         @ActivityInfo.Config int mKnownConfigChanges;
 
         /** These are just extra info. They aren't used for change-detection. */
@@ -2197,6 +2270,7 @@
             mShowWallpaper = origState.showWallpaper();
             mRotation = origState.getWindowConfiguration().getRotation();
             mStartParent = origState.getParent();
+            mDisplayId = getDisplayId(origState);
         }
 
         @VisibleForTesting
@@ -2228,7 +2302,8 @@
                     // assume no change in windowing-mode.
                     || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
                     || !mContainer.getBounds().equals(mAbsoluteBounds)
-                    || mRotation != mContainer.getWindowConfiguration().getRotation();
+                    || mRotation != mContainer.getWindowConfiguration().getRotation()
+                    || mDisplayId != getDisplayId(mContainer);
         }
 
         @TransitionInfo.TransitionMode
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index bacc6e6..86bb6b5 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -107,6 +107,9 @@
      */
     private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
 
+    /** The currently finishing transition. */
+    Transition mFinishingTransition;
+
     /**
      * The windows that request to be invisible while it is in transition. After the transition
      * is finished and the windows are no longer animating, their surfaces will be destroyed.
@@ -313,6 +316,11 @@
         return false;
     }
 
+    /** Returns {@code true} if the `wc` is a participant of the finishing transition. */
+    boolean inFinishingTransition(WindowContainer<?> wc) {
+        return mFinishingTransition != null && mFinishingTransition.mParticipants.contains(wc);
+    }
+
     /** @return {@code true} if a transition is running */
     boolean inTransition() {
         // TODO(shell-transitions): eventually properly support multiple
@@ -358,11 +366,11 @@
     }
 
     boolean isTransientHide(@NonNull Task task) {
-        if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) {
+        if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
         }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
-            if (mPlayingTransitions.get(i).isTransientHide(task)) return true;
+            if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
         }
         return false;
     }
@@ -672,14 +680,13 @@
     }
 
     /** @see Transition#finishTransition */
-    void finishTransition(@NonNull IBinder token) {
+    void finishTransition(Transition record) {
         // It is usually a no-op but make sure that the metric consumer is removed.
-        mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */);
+        mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */);
         // It is a no-op if the transition did not change the display.
         mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-        final Transition record = Transition.fromBinder(token);
-        if (record == null || !mPlayingTransitions.contains(record)) {
-            Slog.e(TAG, "Trying to finish a non-playing transition " + token);
+        if (!mPlayingTransitions.contains(record)) {
+            Slog.e(TAG, "Trying to finish a non-playing transition " + record);
             return;
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 45cdacd..42d23e7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -186,7 +186,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.configstore.V1_0.OptionalBool;
@@ -7361,14 +7360,6 @@
                     .setPointerIconType(PointerIcon.TYPE_DEFAULT);
         }
     }
-
-    PointF getLatestMousePosition() {
-        synchronized (mMousePositionTracker) {
-            return new PointF(mMousePositionTracker.mLatestMouseX,
-                    mMousePositionTracker.mLatestMouseY);
-        }
-    }
-
     void setMousePointerDisplayId(int displayId) {
         mMousePositionTracker.setPointerDisplayId(displayId);
     }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c3c87af..17d4f1b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -135,7 +135,7 @@
      */
     static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
             | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE
-            | ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+            | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_DENSITY;
     static final int CONTROLLABLE_WINDOW_CONFIGS = WINDOW_CONFIG_BOUNDS
             | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
 
@@ -391,9 +391,14 @@
                 // apply the incoming transaction before finish in case it alters the visibility
                 // of the participants.
                 if (t != null) {
+                    // Set the finishing transition before applyTransaction so the visibility
+                    // changes of the transition participants will only set visible-requested
+                    // and still let finishTransition handle the participants.
+                    mTransitionController.mFinishingTransition = transition;
                     applyTransaction(t, syncId, null /*transition*/, caller, transition);
                 }
-                getTransitionController().finishTransition(transitionToken);
+                mTransitionController.finishTransition(transition);
+                mTransitionController.mFinishingTransition = null;
                 if (syncId >= 0) {
                     setSyncReady(syncId);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 694f1be..834b708 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1381,6 +1381,13 @@
     }
 
     /**
+     * Destroys the WindwoProcessController, after the process has been removed.
+     */
+    void destroy() {
+        unregisterConfigurationListeners();
+    }
+
+    /**
      * Check if activity configuration override for the activity process needs an update and perform
      * if needed. By default we try to override the process configuration to match the top activity
      * config to increase app compatibility with multi-window and multi-display. The process will
diff --git a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
index 2767972..424b043 100644
--- a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
+++ b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
@@ -19,8 +19,8 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
-import java.util.Map;
 import java.util.HashMap;
+import java.util.Map;
 
 final class WindowProcessControllerMap {
 
@@ -67,6 +67,7 @@
             mPidMap.remove(pid);
             // remove process from mUidMap
             removeProcessFromUidMap(proc);
+            proc.destroy();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f86b997..d6c0311 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -221,8 +221,6 @@
 import android.view.IWindowFocusObserver;
 import android.view.IWindowId;
 import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -572,12 +570,6 @@
     boolean mRemoveOnExit;
 
     /**
-     * Whether the app died while it was visible, if true we might need
-     * to continue to show it until it's restarted.
-     */
-    boolean mAppDied;
-
-    /**
      * Set when the orientation is changing and this window has not yet
      * been updated for the new orientation.
      */
@@ -760,7 +752,6 @@
      */
     private InsetsState mFrozenInsetsState;
 
-    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
     private KeyInterceptionInfo mKeyInterceptionInfo;
 
     /**
@@ -1504,13 +1495,6 @@
                 }
             }
 
-            // If it's a dead window left on screen, and the configuration changed, there is nothing
-            // we can do about it. Remove the window now.
-            if (mActivityRecord != null && mAppDied) {
-                mActivityRecord.removeDeadWindows();
-                return;
-            }
-
             onResizeHandled();
             mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
@@ -2009,7 +1993,7 @@
     boolean isInteresting() {
         final RecentsAnimationController recentsAnimationController =
                 mWmService.getRecentsAnimationController();
-        return mActivityRecord != null && !mAppDied
+        return mActivityRecord != null
                 && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
                 && mViewVisibility == View.VISIBLE
                 && (recentsAnimationController == null
@@ -2448,11 +2432,6 @@
 
     @Override
     void removeIfPossible() {
-        super.removeIfPossible();
-        removeIfPossible(false /*keepVisibleDeadWindow*/);
-    }
-
-    private void removeIfPossible(boolean keepVisibleDeadWindow) {
         mWindowRemovalAllowed = true;
         ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                 "removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
@@ -2527,21 +2506,6 @@
                 // If we are not currently running the exit animation, we need to see about starting one
                 wasVisible = isVisible();
 
-                if (keepVisibleDeadWindow) {
-                    ProtoLog.v(WM_DEBUG_ADD_REMOVE,
-                            "Not removing %s because app died while it's visible", this);
-
-                    mAppDied = true;
-                    setDisplayLayoutNeeded();
-                    mWmService.mWindowPlacerLocked.performSurfacePlacement();
-
-                    // Set up a replacement input channel since the app is now dead.
-                    // We need to catch tapping on the dead window to restart the app.
-                    openInputChannel(null);
-                    displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
-                    return;
-                }
-
                 // Remove immediately if there is display transition because the animation is
                 // usually unnoticeable (e.g. covered by rotation animation) and the animation
                 // bounds could be inconsistent, such as depending on when the window applies
@@ -2715,19 +2679,7 @@
                 || (isVisible() && mActivityRecord != null && mActivityRecord.isVisible());
     }
 
-    private final class DeadWindowEventReceiver extends InputEventReceiver {
-        DeadWindowEventReceiver(InputChannel inputChannel) {
-            super(inputChannel, mWmService.mH.getLooper());
-        }
-        @Override
-        public void onInputEvent(InputEvent event) {
-            finishInputEvent(event, true);
-        }
-    }
-    /** Fake event receiver for windows that died visible. */
-    private DeadWindowEventReceiver mDeadWindowEventReceiver;
-
-    void openInputChannel(InputChannel outInputChannel) {
+    void openInputChannel(@NonNull InputChannel outInputChannel) {
         if (mInputChannel != null) {
             throw new IllegalStateException("Window already has an input channel.");
         }
@@ -2736,14 +2688,7 @@
         mInputChannelToken = mInputChannel.getToken();
         mInputWindowHandle.setToken(mInputChannelToken);
         mWmService.mInputToWindowMap.put(mInputChannelToken, this);
-        if (outInputChannel != null) {
-            mInputChannel.copyTo(outInputChannel);
-        } else {
-            // If the window died visible, we setup a fake input channel, so that taps
-            // can still detected by input monitor channel, and we can relaunch the app.
-            // Create fake event receiver that simply reports all events as handled.
-            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
-        }
+        mInputChannel.copyTo(outInputChannel);
     }
 
     /**
@@ -2754,10 +2699,6 @@
     }
 
     void disposeInputChannel() {
-        if (mDeadWindowEventReceiver != null) {
-            mDeadWindowEventReceiver.dispose();
-            mDeadWindowEventReceiver = null;
-        }
         if (mInputChannelToken != null) {
             // Unregister server channel first otherwise it complains about broken channel.
             mWmService.mInputManager.removeInputChannel(mInputChannelToken);
@@ -3084,11 +3025,10 @@
                             .windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
-                        final DisplayContent dc = getDisplayContent();
                         if (win.mActivityRecord != null && win.mActivityRecord.findMainWindow() == win) {
                             mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord);
                         }
-                        win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
+                        win.removeIfPossible();
                     } else if (mHasSurface) {
                         Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
                         WindowState.this.removeIfPossible();
@@ -3100,32 +3040,6 @@
         }
     }
 
-    /**
-     * Returns true if this window is visible and belongs to a dead app and shouldn't be removed,
-     * because we want to preserve its location on screen to be re-activated later when the user
-     * interacts with it.
-     */
-    private boolean shouldKeepVisibleDeadAppWindow() {
-        if (!isVisible() || mActivityRecord == null || !mActivityRecord.isClientVisible()) {
-            // Not a visible app window or the app isn't dead.
-            return false;
-        }
-
-        if (mAttrs.token != mClient.asBinder()) {
-            // The window was add by a client using another client's app token. We don't want to
-            // keep the dead window around for this case since this is meant for 'real' apps.
-            return false;
-        }
-
-        if (mAttrs.type == TYPE_APPLICATION_STARTING) {
-            // We don't keep starting windows since they were added by the window manager before
-            // the app even launched.
-            return false;
-        }
-
-        return getWindowConfiguration().keepVisibleDeadAppWindowOnScreen();
-    }
-
     /** Returns {@code true} if this window desires key events. */
     boolean canReceiveKeys() {
         return canReceiveKeys(false /* fromUserTouch */);
@@ -3972,7 +3886,7 @@
     @Override
     public void notifyInsetsControlChanged() {
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
-        if (mAppDied || mRemoved) {
+        if (mRemoved) {
             return;
         }
         final InsetsStateController stateController =
@@ -4278,7 +4192,6 @@
             pw.println(prefix + "mToken=" + mToken);
             if (mActivityRecord != null) {
                 pw.println(prefix + "mActivityRecord=" + mActivityRecord);
-                pw.print(prefix + "mAppDied=" + mAppDied);
                 pw.print(prefix + "drawnStateEvaluated=" + getDrawnStateEvaluated());
                 pw.println(prefix + "mightAffectAllDrawn=" + mightAffectAllDrawn());
             }
@@ -5407,10 +5320,7 @@
     }
 
     private void applyDims() {
-        if (!mAnimatingExit && mAppDied) {
-            mIsDimming = true;
-            getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
-        } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
+        if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
                    && isVisibleNow() && !mHidden) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index b4e2fb6..da44da4 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -308,6 +308,7 @@
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
+    FloatPoint getMouseCursorPosition();
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -366,7 +367,7 @@
     virtual PointerIconStyle getDefaultPointerIconId();
     virtual PointerIconStyle getDefaultStylusIconId();
     virtual PointerIconStyle getCustomPointerIconId();
-    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
+    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position);
 
     /* --- If touch mode is enabled per display or global --- */
 
@@ -730,11 +731,11 @@
     return controller;
 }
 
-void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
-                                                   float yPos) {
+void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId,
+                                                   const FloatPoint& position) {
     JNIEnv* env = jniEnv();
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
-                        xPos, yPos);
+                        position.x, position.y);
     checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
 }
 
@@ -1655,6 +1656,14 @@
     return static_cast<bool>(enabled);
 }
 
+FloatPoint NativeInputManager::getMouseCursorPosition() {
+    AutoMutex _l(mLock);
+    const auto pc = mLocked.pointerController.lock();
+    if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
+
+    return pc->getPosition();
+}
+
 // ----------------------------------------------------------------------------
 
 static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2547,6 +2556,15 @@
     im->setStylusButtonMotionEventsEnabled(enabled);
 }
 
+static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    const auto p = im->getMouseCursorPosition();
+    const std::array<float, 2> arr = {{p.x, p.y}};
+    jfloatArray outArr = env->NewFloatArray(2);
+    env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
+    return outArr;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2640,6 +2658,7 @@
         {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
         {"setStylusButtonMotionEventsEnabled", "(Z)V",
          (void*)nativeSetStylusButtonMotionEventsEnabled},
+        {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
 };
 
 #define FIND_CLASS(var, className) \
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 94260e20..e09c0a2 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -122,7 +122,7 @@
     private void respondToClientWithResponseAndFinish() {
         Log.i(TAG, "respondToClientWithResponseAndFinish");
         if (isSessionCancelled()) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
                     ApiStatus.CLIENT_CANCELED);
@@ -134,7 +134,7 @@
             logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
                     ApiStatus.SUCCESS);
         } catch (RemoteException e) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             Log.i(TAG, "Issue while propagating the response to the client");
             logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 47b8c7d..793d83e 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -83,6 +83,7 @@
 
     @Override
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
         try {
             mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newCreateRequestInfo(
@@ -90,6 +91,7 @@
                             mClientAppInfo.getPackageName()),
                     providerDataList));
         } catch (RemoteException e) {
+            mChosenProviderFinalPhaseMetric.setUiReturned(false);
             respondToClientWithErrorAndFinish(
                     CreateCredentialException.TYPE_UNKNOWN,
                     "Unable to invoke selector");
@@ -99,14 +101,16 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable CreateCredentialResponse response) {
+        mChosenProviderFinalPhaseMetric.setUiReturned(true);
+        mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
         Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
         setChosenMetric(componentName);
         if (response != null) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             respondToClientWithResponseAndFinish(response);
         } else {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
                     "Invalid response");
@@ -138,6 +142,8 @@
 
     private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
         Log.i(TAG, "respondToClientWithResponseAndFinish");
+        // TODO immediately add exception bit to chosen provider and do final emits across all
+        // including sequenceCounter!
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Log.i(TAG, "Request has already been completed. This is strange.");
             return;
@@ -162,6 +168,7 @@
 
     private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
         Log.i(TAG, "respondToClientWithErrorAndFinish");
+
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Log.i(TAG, "Request has already been completed. This is strange.");
             return;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 10d3dc0..4c5c366 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -41,6 +41,7 @@
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetPendingCredentialCallback;
 import android.credentials.ISetEnabledProvidersCallback;
 import android.credentials.RegisterCredentialDescriptionRequest;
 import android.credentials.UnregisterCredentialDescriptionRequest;
@@ -438,6 +439,17 @@
             return cancelTransport;
         }
 
+        @Override
+        public ICancellationSignal executeGetPendingCredential(
+                GetCredentialRequest request,
+                IGetPendingCredentialCallback callback,
+                final String callingPackage) {
+            // TODO(b/273308895): implement
+
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            return cancelTransport;
+        }
+
         private void processGetCredential(
                 GetCredentialRequest request,
                 IGetCredentialCallback callback,
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 8e90c09..00fbbba 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -82,12 +82,14 @@
 
     @Override
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
         try {
             mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
                     mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
                     providerDataList));
         } catch (RemoteException e) {
+            mChosenProviderFinalPhaseMetric.setUiReturned(false);
             respondToClientWithErrorAndFinish(
                     GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
         }
@@ -96,14 +98,16 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable GetCredentialResponse response) {
+        mChosenProviderFinalPhaseMetric.setUiReturned(true);
+        mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
         Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
         setChosenMetric(componentName);
         if (response != null) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             respondToClientWithResponseAndFinish(response);
         } else {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
                     "Invalid response from provider");
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 1b3e37a..ed139b5 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -25,7 +25,7 @@
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
 import com.android.server.credentials.metrics.InitialPhaseMetric;
 
 import java.util.Map;
@@ -89,11 +89,11 @@
      * @param apiStatus            the api status to log
      * @param providers            a map with known providers
      * @param callingUid           the calling UID of the client app
-     * @param chosenProviderMetric the metric data type of the final chosen provider
+     * @param chosenProviderFinalPhaseMetric the metric data type of the final chosen provider
      */
     protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
             Map<String, ProviderSession> providers, int callingUid,
-            ChosenProviderMetric chosenProviderMetric) {
+            ChosenProviderFinalPhaseMetric chosenProviderFinalPhaseMetric) {
         try {
             var providerSessions = providers.values();
             int providerSize = providerSessions.size();
@@ -102,7 +102,7 @@
             int[] candidateStatusList = new int[providerSize];
             int index = 0;
             for (var session : providerSessions) {
-                CandidatePhaseMetric metric = session.mCandidateProviderMetric;
+                CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
                 candidateUidList[index] = metric.getCandidateUid();
                 candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
                 candidateStatusList[index] = metric.getProviderQueryStatus();
@@ -116,12 +116,13 @@
                     /* repeated_candidate_provider_round_trip_time_query_microseconds */
                     candidateQueryRoundTripTimeList,
                     /* repeated_candidate_provider_status */ candidateStatusList,
-                    /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
+                    /* chosen_provider_uid */ chosenProviderFinalPhaseMetric.getChosenUid(),
                     /* chosen_provider_round_trip_time_overall_microseconds */
-                    chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
+                    chosenProviderFinalPhaseMetric.getEntireProviderLatencyMicroseconds(),
                     /* chosen_provider_final_phase_microseconds (backwards compat only) */
                     DEFAULT_INT_32,
-                    /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
+                    /* chosen_provider_status */ chosenProviderFinalPhaseMetric
+                            .getChosenProviderStatus());
         } catch (Exception e) {
             Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index ab29acc..950cf4f 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -120,7 +120,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            /*
+            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
+            TODO immediately once the other change patched through
+            mCandidateProviderMetric.setSessionId(initMetric
+            .mInitialPhaseMetric.getSessionId());
+            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
+             */
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
             mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8c9c6cf..3ec0fc0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -226,7 +226,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            /*
+            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
+            TODO immediately once the other change patched through
+            mCandidateProviderMetric.setSessionId(initMetric
+            .mInitialPhaseMetric.getSessionId());
+            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
+             */
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
             mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 955b721..ec8bf22 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -61,13 +61,13 @@
         RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> {
     private static final String TAG = "ProviderGetSession";
     // Key to be used as the entry key for an action entry
-    private static final String ACTION_ENTRY_KEY = "action_key";
+    public static final String ACTION_ENTRY_KEY = "action_key";
     // Key to be used as the entry key for the authentication entry
-    private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
+    public static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
     // Key to be used as an entry key for a remote entry
-    private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
+    public static final String REMOTE_ENTRY_KEY = "remote_entry_key";
     // Key to be used as an entry key for a credential entry
-    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+    public static final String CREDENTIAL_ENTRY_KEY = "credential_key";
 
     @NonNull
     private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
@@ -269,7 +269,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            /*
+            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
+            TODO immediately once the other change patched through
+            mCandidateProviderMetric.setSessionId(initMetric
+            .mInitialPhaseMetric.getSessionId());
+            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
+             */
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
             mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
         }
     }
@@ -427,10 +434,13 @@
     private void onSetInitialRemoteResponse(BeginGetCredentialResponse response) {
         mProviderResponse = response;
         addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
+        // Log the data.
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
             return;
         }
+        // TODO immediately, add to Candidate Phase counts, repeat across all sessions
+        // Use sets to dedup type counts
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 03e2a32..77d4e77 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -38,6 +38,7 @@
 
 /**
  * Provider session storing the state of provider response and ui entries.
+ *
  * @param <T> The request to be sent to the provider
  * @param <R> The response to be expected from the provider
  */
@@ -46,21 +47,35 @@
 
     private static final String TAG = "ProviderSession";
 
-    @NonNull protected final Context mContext;
-    @NonNull protected final ComponentName mComponentName;
-    @Nullable protected final CredentialProviderInfo mProviderInfo;
-    @Nullable protected final RemoteCredentialService mRemoteCredentialService;
-    @NonNull protected final int mUserId;
-    @NonNull protected Status mStatus = Status.NOT_STARTED;
-    @Nullable protected final ProviderInternalCallback mCallbacks;
-    @Nullable protected Credential mFinalCredentialResponse;
-    @Nullable protected ICancellationSignal mProviderCancellationSignal;
-    @NonNull protected final T mProviderRequest;
-    @Nullable protected R mProviderResponse;
-    @NonNull protected Boolean mProviderResponseSet = false;
+    @NonNull
+    protected final Context mContext;
+    @NonNull
+    protected final ComponentName mComponentName;
+    @Nullable
+    protected final CredentialProviderInfo mProviderInfo;
+    @Nullable
+    protected final RemoteCredentialService mRemoteCredentialService;
+    @NonNull
+    protected final int mUserId;
+    @NonNull
+    protected Status mStatus = Status.NOT_STARTED;
+    @Nullable
+    protected final ProviderInternalCallback mCallbacks;
+    @Nullable
+    protected Credential mFinalCredentialResponse;
+    @Nullable
+    protected ICancellationSignal mProviderCancellationSignal;
+    @NonNull
+    protected final T mProviderRequest;
+    @Nullable
+    protected R mProviderResponse;
+    @NonNull
+    protected Boolean mProviderResponseSet = false;
     // Specific candidate provider metric for the provider this session handles
-    @Nullable protected CandidatePhaseMetric mCandidateProviderMetric;
-    @NonNull private int mProviderSessionUid;
+    @Nullable
+    protected CandidatePhaseMetric mCandidatePhasePerProviderMetric;
+    @NonNull
+    private int mProviderSessionUid;
 
     /**
      * Returns true if the given status reflects that the provider state is ready to be shown
@@ -100,6 +115,7 @@
      * Interface to be implemented by any class that wishes to get a callback when a particular
      * provider session's status changes. Typically, implemented by the {@link RequestSession}
      * class.
+     *
      * @param <V> the type of the final response expected
      */
     public interface ProviderInternalCallback<V> {
@@ -127,7 +143,7 @@
         mUserId = userId;
         mComponentName = componentName;
         mRemoteCredentialService = remoteCredentialService;
-        mCandidateProviderMetric = new CandidatePhaseMetric();
+        mCandidatePhasePerProviderMetric = new CandidatePhaseMetric();
         mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
     }
 
@@ -158,7 +174,7 @@
     }
 
     public Credential getFinalCredentialResponse() {
-        return  mFinalCredentialResponse;
+        return mFinalCredentialResponse;
     }
 
     /** Propagates cancellation signal to the remote provider service. */
@@ -192,7 +208,7 @@
         return mRemoteCredentialService;
     }
 
-    /** Updates the status .*/
+    /** Updates the status . */
     protected void updateStatusAndInvokeCallback(@NonNull Status status) {
         setStatus(status);
         updateCandidateMetric(status);
@@ -200,15 +216,18 @@
     }
 
     private void updateCandidateMetric(Status status) {
-        mCandidateProviderMetric.setCandidateUid(mProviderSessionUid);
-        mCandidateProviderMetric
+        mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
+        // TODO immediately update the candidate phase here to have more new data
+        mCandidatePhasePerProviderMetric
                 .setQueryFinishTimeNanoseconds(System.nanoTime());
         if (isTerminatingStatus(status)) {
-            mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE
-                    .getMetricCode());
+            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                    ProviderStatusForMetrics.QUERY_FAILURE
+                            .getMetricCode());
         } else if (isCompletionStatus(status)) {
-            mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS
-                    .getMetricCode());
+            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                    ProviderStatusForMetrics.QUERY_SUCCESS
+                            .getMetricCode());
         }
     }
 
@@ -228,7 +247,8 @@
     }
 
     /** Update the response state stored with the provider session. */
-    @Nullable protected R getProviderResponse() {
+    @Nullable
+    protected R getProviderResponse() {
         return mProviderResponse;
     }
 
@@ -265,15 +285,20 @@
         return false;
     }
 
-    /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
-     * shown on the UI. */
-    @Nullable protected abstract ProviderData prepareUiData();
+    /**
+     * Should be overridden to prepare, and stores state for {@link ProviderData} to be
+     * shown on the UI.
+     */
+    @Nullable
+    protected abstract ProviderData prepareUiData();
 
     /** Should be overridden to handle the selected entry from the UI. */
     protected abstract void onUiEntrySelected(String entryType, String entryId,
             ProviderPendingIntentResponse providerPendingIntentResponse);
 
-    /** Should be overridden to invoke the provider at a defined location. Helpful for
-     * situations such as metric generation. */
+    /**
+     * Should be overridden to invoke the provider at a defined location. Helpful for
+     * situations such as metric generation.
+     */
     protected abstract void invokeSession();
 }
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index edddba0..5dc7adf 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -36,12 +36,15 @@
 import com.android.internal.R;
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
+import com.android.server.credentials.metrics.EntryEnum;
 import com.android.server.credentials.metrics.InitialPhaseMetric;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -75,8 +78,16 @@
     protected final CancellationSignal mCancellationSignal;
 
     protected final Map<String, ProviderSession> mProviders = new HashMap<>();
-    protected ChosenProviderMetric mChosenProviderMetric = new ChosenProviderMetric();
     protected InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+    protected ChosenProviderFinalPhaseMetric
+            mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
+
+    // TODO(b/271135048) - Group metrics used in a scope together, such as here in RequestSession
+    // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
+    @NonNull
+    protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
+    // As emits occur in sequential order, increment this counter and utilize
+    protected int mSequenceCounter = 0;
     protected final String mHybridService;
 
     @NonNull
@@ -152,10 +163,22 @@
             return;
         }
         Log.i(TAG, "Provider session found");
+        logBrowsingPhasePerSelect(selection, providerSession);
         providerSession.onUiEntrySelected(selection.getEntryKey(),
                 selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
     }
 
+    private void logBrowsingPhasePerSelect(UserSelectionDialogResult selection,
+            ProviderSession providerSession) {
+        CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
+        browsingPhaseMetric.setSessionId(this.mInitialPhaseMetric.getSessionId());
+        browsingPhaseMetric.setEntryEnum(
+                EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
+        browsingPhaseMetric.setProviderUid(providerSession.mCandidatePhasePerProviderMetric
+                .getCandidateUid());
+        this.mCandidateBrowsingPhaseMetric.add(new CandidateBrowsingPhaseMetric());
+    }
+
     protected void finishSession(boolean propagateCancellation) {
         Log.i(TAG, "finishing session");
         if (propagateCancellation) {
@@ -176,7 +199,14 @@
 
     protected void logApiCall(ApiName apiName, ApiStatus apiStatus) {
         logApiCalled(apiName, apiStatus, mProviders, mCallingUid,
-                mChosenProviderMetric);
+                mChosenProviderFinalPhaseMetric);
+    }
+
+    protected void logApiCall(ChosenProviderFinalPhaseMetric finalPhaseMetric,
+            List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics) {
+        // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
+        // For the returned types by authentication entries - i.e. a CandidatePhase During Browse
+        // TODO call MetricUtilities with new setup
     }
 
     protected boolean isSessionCancelled() {
@@ -218,8 +248,10 @@
         }
         if (!providerDataList.isEmpty()) {
             Log.i(TAG, "provider list not empty about to initiate ui");
+            // TODO immediately Add paths to end it (say it fails)
             if (isSessionCancelled()) {
                 Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled");
+                // TODO immedaitely Add paths
             } else {
                 launchUiWithProviderData(providerDataList);
             }
@@ -233,11 +265,21 @@
      */
     protected void setChosenMetric(ComponentName componentName) {
         CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
-                .mCandidateProviderMetric;
-        mChosenProviderMetric.setChosenUid(metric.getCandidateUid());
-        mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
-        mChosenProviderMetric.setQueryPhaseLatencyMicroseconds(
+                .mCandidatePhasePerProviderMetric;
+
+        mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
+        mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
+
+        mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
                 metric.getQueryLatencyMicroseconds());
-        mChosenProviderMetric.setQueryStartTimeNanoseconds(metric.getStartQueryTimeNanoseconds());
+
+        mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
+                metric.getServiceBeganTimeNanoseconds());
+        mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
+                metric.getStartQueryTimeNanoseconds());
+
+        // TODO immediately update with the entry count numbers from the candidate metrics
+
+        mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
index 37ec8f0..0e1e0389 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -17,23 +17,22 @@
 package com.android.server.credentials.metrics;
 
 /**
- * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderMetric}. The user is
- * shown various entries from the provider responses, and may selectively browse through many
- * entries. It is possible that the initial set of browsing is for a provider that is ultimately
- * not chosen. This metric will be gathered PER browsing click, and aggregated, so that we can
- * understand where user interaction is more cumbersome, informing us for future improvements. This
- * can only be complete when the browsing is finished, ending in a final user choice, or possibly
- * a cancellation. Thus, this will be collected and emitted in the final phase, though collection
- * will begin in the candidate phase when the user begins browsing options.
+ * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderFinalPhaseMetric}.
+ * The user is shown various entries from the provider responses, and may selectively browse through
+ * many entries. It is possible that the initial set of browsing is for a provider that is
+ * ultimately not chosen. This metric will be gathered PER browsing click, and aggregated, so that
+ * we can understand where user interaction is more cumbersome, informing us for future
+ * improvements. This can only be complete when the browsing is finished, ending in a final user
+ * choice, or possibly a cancellation. Thus, this will be collected and emitted in the final phase,
+ * though collection will begin in the candidate phase when the user begins browsing options.
  */
 public class CandidateBrowsingPhaseMetric {
 
-    private static final String TAG = "CandidateSelectionPhaseMetric";
-    private static final int SEQUENCE_ID = 3;
+    private static final String TAG = "CandidateBrowsingPhaseMetric";
     // The session id associated with the API Call this candidate provider is a part of, default -1
     private int mSessionId = -1;
-    // The EntryEnum that was pressed, defaults to -1 (TODO immediately, generate entry enum).
-    private int mEntryEnum = -1;
+    // The EntryEnum that was pressed, defaults to -1
+    private int mEntryEnum = EntryEnum.UNKNOWN.getMetricCode();
     // The provider associated with the press, defaults to -1
     private int mProviderUid = -1;
 
@@ -47,12 +46,6 @@
         return mSessionId;
     }
 
-    /* -- The sequence ID -- */
-
-    public int getSequenceId() {
-        return SEQUENCE_ID;
-    }
-
     /* -- The Entry of this tap -- */
 
     public void setEntryEnum(int entryEnum) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 1c7fb69..c392d78 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -30,10 +30,8 @@
 public class CandidatePhaseMetric {
 
     private static final String TAG = "CandidateProviderMetric";
-    // Since this will always be the second in the split sequence, this is statically 2
-    private static final int SESSION_ID = 2;
-    // The sequence number of this emit of the API call, default -1, equal for all candidates
-    private int mSequenceId = -1;
+    // The session id of this provider, default set to -1
+    private int mSessionId = -1;
     // Indicates if this provider returned from the query phase, default false
     private boolean mQueryReturned = false;
 
@@ -150,18 +148,13 @@
     }
 
     /* -------------- Session Id ---------------- */
+
+    public void setSessionId(int sessionId) {
+        mSessionId = sessionId;
+    }
+
     public int getSessionId() {
-        return SESSION_ID;
-    }
-
-    /* -------------- Sequence Id ---------------- */
-
-    public void setSequenceId(int sequenceId) {
-        mSequenceId = sequenceId;
-    }
-
-    public int getSequenceId() {
-        return mSequenceId;
+        return mSessionId;
     }
 
     /* -------------- Query Returned Status ---------------- */
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
similarity index 80%
rename from services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
rename to services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 1a61091..32fe204 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -21,16 +21,22 @@
 import com.android.server.credentials.MetricUtilities;
 
 /**
- * The central chosen provider metric object that mimics our defined metric setup.
+ * The central chosen provider metric object that mimics our defined metric setup. This is used
+ * in the final phase of the flow and emits final status metrics.
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
  * TODO(b/270403549) - iterate on this in V3+
+ * TODO(Immediately) - finalize V3 only types
  */
-public class ChosenProviderMetric {
+public class ChosenProviderFinalPhaseMetric {
 
-    // TODO(b/270403549) - applies elsewhere, likely removed or replaced with a count-index (1,2,3)
-    private static final String TAG = "ChosenProviderMetric";
+    // TODO(b/270403549) - applies elsewhere, likely removed or replaced w/ some hashed/count index
+    private static final String TAG = "ChosenFinalPhaseMetric";
+    // The session id associated with this API call, used to unite split emits
+    private long mSessionId = -1;
+    // Reveals if the UI was returned, false by default
+    private boolean mUiReturned = false;
     private int mChosenUid = -1;
 
     // Latency figures typically fed in from prior CandidateProviderMetric
@@ -39,16 +45,29 @@
     private int mQueryPhaseLatencyMicroseconds = -1;
 
     // Timestamps kept in raw nanoseconds. Expected to be converted to microseconds from using
-    // reference 'mServiceBeganTimeNanoseconds' during metric log point.
+    // reference 'mServiceBeganTimeNanoseconds' during metric log point
 
+    // Kept for local reference purposes, the initial timestamp of the service called passed in
     private long mServiceBeganTimeNanoseconds = -1;
+    // The first query timestamp, which upon emit is normalized to microseconds using the reference
+    // start timestamp
     private long mQueryStartTimeNanoseconds = -1;
+    // The UI call timestamp, which upon emit will be normalized to microseconds using reference
     private long mUiCallStartTimeNanoseconds = -1;
+    // The UI return timestamp, which upon emit will be normalized to microseconds using reference
     private long mUiCallEndTimeNanoseconds = -1;
+    // The final finish timestamp, which upon emit will be normalized to microseconds with reference
     private long mFinalFinishTimeNanoseconds = -1;
-    private int mChosenProviderStatus = -1;
+    // The status of this provider after selection
 
-    public ChosenProviderMetric() {
+    // Other General Information, such as final api status, provider status, entry info, etc...
+
+    private int mChosenProviderStatus = -1;
+    // TODO add remaining properties based on the Atom ; specifically, migrate the candidate
+    // Entry information, and store final status here
+
+
+    public ChosenProviderFinalPhaseMetric() {
     }
 
     /* ------------------- UID ------------------- */
@@ -200,4 +219,24 @@
     public void setChosenProviderStatus(int chosenProviderStatus) {
         mChosenProviderStatus = chosenProviderStatus;
     }
+
+    /* ----------- Session ID -------------- */
+
+    public void setSessionId(long sessionId) {
+        mSessionId = sessionId;
+    }
+
+    public long getSessionId() {
+        return mSessionId;
+    }
+
+    /* ----------- UI Returned Successfully -------------- */
+
+    public void setUiReturned(boolean uiReturned) {
+        mUiReturned = uiReturned;
+    }
+
+    public boolean isUiReturned() {
+        return mUiReturned;
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
new file mode 100644
index 0000000..73403a6
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -0,0 +1,86 @@
+/*
+ * 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.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
+import static com.android.server.credentials.ProviderGetSession.ACTION_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.AUTHENTICATION_ACTION_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.CREDENTIAL_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.REMOTE_ENTRY_KEY;
+
+import android.util.Log;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
+public enum EntryEnum {
+    // TODO immediately, update with built entries
+    UNKNOWN(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+    ACTION_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL),
+    CREDENTIAL_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL),
+    REMOTE_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL),
+    AUTHENTICATION_ENTRY(
+            CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE
+    );
+
+    private static final String TAG = "EntryEnum";
+
+    private final int mInnerMetricCode;
+
+    private static final Map<String, Integer> sKeyToEntryCode = Map.ofEntries(
+            new AbstractMap.SimpleEntry<>(ACTION_ENTRY_KEY,
+                    ACTION_ENTRY.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(AUTHENTICATION_ACTION_ENTRY_KEY,
+                    AUTHENTICATION_ENTRY.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(REMOTE_ENTRY_KEY,
+                    REMOTE_ENTRY.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(CREDENTIAL_ENTRY_KEY,
+                    CREDENTIAL_ENTRY.mInnerMetricCode)
+    );
+
+    EntryEnum(int innerMetricCode) {
+        this.mInnerMetricCode = innerMetricCode;
+    }
+
+    /**
+     * Gives the West-world version of the metric name.
+     *
+     * @return a code corresponding to the west world metric name
+     */
+    public int getMetricCode() {
+        return this.mInnerMetricCode;
+    }
+
+    /**
+     * Given a string key type known to the framework, this returns the known metric code associated
+     * with that string.
+     *
+     * @param stringKey a string key type for a particular entry
+     * @return the metric code associated with this enum
+     */
+    public static int getMetricCodeFromString(String stringKey) {
+        if (!sKeyToEntryCode.containsKey(stringKey)) {
+            Log.w(TAG, "Attempted to use an unsupported string key entry type");
+            return UNKNOWN.mInnerMetricCode;
+        }
+        return sKeyToEntryCode.get(stringKey);
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 31c6f6f..a73495f 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -24,16 +24,14 @@
  * TODO(b/270403549) - iterate on this in V3+
  */
 public class InitialPhaseMetric {
-    private static final String TAG = "PreCandidateMetric";
-    // A sequence id to order united emits, due to split, this will statically always be 1
-    public static final int SEQUENCE_ID = 1;
+    private static final String TAG = "InitialPhaseMetric";
 
     // The api being called, default set to unknown
     private int mApiName = ApiName.UNKNOWN.getMetricCode();
     // The caller uid of the calling application, default to -1
     private int mCallerUid = -1;
     // The session id to unite multiple atom emits, default to -1
-    private long mSessionId = -1;
+    private int mSessionId = -1;
     private int mCountRequestClassType = -1;
 
     // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
@@ -100,15 +98,14 @@
 
     /* ------ SessionId ------ */
 
-    public void setSessionId(long sessionId) {
+    public void setSessionId(int sessionId) {
         mSessionId = sessionId;
     }
 
-    public long getSessionId() {
+    public int getSessionId() {
         return mSessionId;
     }
 
-
     /* ------ Count Request Class Types ------ */
 
     public void setCountRequestClassType(int countRequestClassType) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6cd9f1c..a1789b2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3224,7 +3224,7 @@
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         Bundle options = new BroadcastOptions()
                 .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-                .setDeferUntilActive(true)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                 .toBundle();
         mInjector.binderWithCleanCallingIdentity(() ->
                 mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle), null, options));
diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java
index 50113fe..b895812 100644
--- a/services/java/com/android/server/HsumBootUserInitializer.java
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -18,6 +18,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ContentResolver;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Handler;
@@ -27,6 +28,7 @@
 import android.provider.Settings;
 
 import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -41,6 +43,7 @@
 
     private final UserManagerInternal mUmi;
     private final ActivityManagerService mAms;
+    private final PackageManagerService mPms;
     private final ContentResolver mContentResolver;
 
     private final ContentObserver mDeviceProvisionedObserver =
@@ -63,20 +66,23 @@
 
     /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */
     public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am,
-            ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+            PackageManagerService pms, ContentResolver contentResolver,
+            boolean shouldAlwaysHaveMainUser) {
 
         if (!UserManager.isHeadlessSystemUserMode()) {
             return null;
         }
         return new HsumBootUserInitializer(
                 LocalServices.getService(UserManagerInternal.class),
-                am, contentResolver, shouldAlwaysHaveMainUser);
+                am, pms, contentResolver, shouldAlwaysHaveMainUser);
     }
 
     private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am,
-            ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+            PackageManagerService pms, ContentResolver contentResolver,
+            boolean shouldAlwaysHaveMainUser) {
         mUmi = umi;
         mAms = am;
+        mPms = pms;
         mContentResolver = contentResolver;
         mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser;
     }
@@ -131,7 +137,8 @@
 
         try {
             t.traceBegin("getBootUser");
-            final int bootUser = mUmi.getBootUser();
+            final int bootUser = mUmi.getBootUser(/* waitUntilSet= */ mPms
+                    .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, /* version= */0));
             t.traceEnd();
             t.traceBegin("switchToBootUser-" + bootUser);
             switchToBootUser(bootUser);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4c31645..b933508 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1793,6 +1793,10 @@
         }
         t.traceEnd();
 
+        t.traceBegin("StartAppHibernationService");
+        mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+        t.traceEnd();
+
         t.traceBegin("ArtManagerLocal");
         DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
         t.traceEnd();
@@ -2316,10 +2320,6 @@
             mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
             t.traceEnd();
 
-            t.traceBegin("StartAppHibernationService");
-            mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
-            t.traceEnd();
-
             if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
                 t.traceBegin("StartGestureLauncher");
                 mSystemServiceManager.startService(GestureLauncherService.class);
@@ -2721,7 +2721,7 @@
         // on it in their setup, but likely needs to be done after LockSettingsService is ready.
         final HsumBootUserInitializer hsumBootUserInitializer =
                 HsumBootUserInitializer.createInstance(
-                        mActivityManagerService, mContentResolver,
+                        mActivityManagerService, mPackageManagerService, mContentResolver,
                         context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin));
         if (hsumBootUserInitializer != null) {
             t.traceBegin("HsumBootUserInitializer.init");
@@ -3021,8 +3021,7 @@
             mSystemServiceManager.startBootPhase(t, SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
             t.traceEnd();
 
-            if (hsumBootUserInitializer != null && !isAutomotive) {
-                // TODO(b/261924826): remove isAutomotive check once the workflow is finalized
+            if (hsumBootUserInitializer != null) {
                 t.traceBegin("HsumBootUserInitializer.systemRunning");
                 hsumBootUserInitializer.systemRunning(t);
                 t.traceEnd();
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
index 2894395..24e380c 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
@@ -32,9 +32,9 @@
 }
 
 android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r1000",
+    name: "FrameworksServicesTests_install_uses_sdk_r10000",
     defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r1000.xml",
+    manifest: "AndroidManifest-r10000.xml",
 }
 
 android_test_helper_app {
@@ -44,9 +44,9 @@
 }
 
 android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r0_s1000",
+    name: "FrameworksServicesTests_install_uses_sdk_r0_s10000",
     defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r0-s1000.xml",
+    manifest: "AndroidManifest-r0-s10000.xml",
 }
 
 android_test_helper_app {
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
index 25743b8..383e60a 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
@@ -19,7 +19,7 @@
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
         <!-- This fails because 31 is not version 5 -->
         <extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
-        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="1000" />
+        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="10000" />
     </uses-sdk>
 
     <application>
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
index 9bf9254..fe7a212 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
@@ -18,7 +18,7 @@
 
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
         <!-- This will fail to install, because minExtensionVersion is not met -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
+        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="10000" />
     </uses-sdk>
 
     <application>
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index e711cab..1146271 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -127,10 +127,10 @@
         ":FrameworksServicesTests_install_uses_sdk_q0",
         ":FrameworksServicesTests_install_uses_sdk_q0_r0",
         ":FrameworksServicesTests_install_uses_sdk_r0",
-        ":FrameworksServicesTests_install_uses_sdk_r1000",
+        ":FrameworksServicesTests_install_uses_sdk_r10000",
         ":FrameworksServicesTests_install_uses_sdk_r_none",
         ":FrameworksServicesTests_install_uses_sdk_r0_s0",
-        ":FrameworksServicesTests_install_uses_sdk_r0_s1000",
+        ":FrameworksServicesTests_install_uses_sdk_r0_s10000",
         ":FrameworksServicesTests_keyset_permdef_sa_unone",
         ":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
         ":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index ebf309f..906cc83 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -575,10 +575,10 @@
         assertEquals(0, minExtVers.get(31, -1));
 
         Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
-        appToError.put(Pair.create("install_uses_sdk.apk_r1000", R.raw.install_uses_sdk_r1000),
+        appToError.put(Pair.create("install_uses_sdk.apk_r10000", R.raw.install_uses_sdk_r10000),
                        PackageManager.INSTALL_FAILED_OLDER_SDK);
         appToError.put(
-                Pair.create("install_uses_sdk.apk_r0_s1000", R.raw.install_uses_sdk_r0_s1000),
+                Pair.create("install_uses_sdk.apk_r0_s10000", R.raw.install_uses_sdk_r0_s10000),
                 PackageManager.INSTALL_FAILED_OLDER_SDK);
 
         appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 83441bf..1a75170 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -68,6 +68,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test RescueParty.
@@ -94,6 +95,9 @@
             "persist.device_config.configuration.disable_rescue_party";
     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
             "persist.device_config.configuration.disable_rescue_party_factory_reset";
+    private static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset";
+
+    private static final int THROTTLING_DURATION_MIN = 10;
 
     private MockitoSession mSession;
     private HashMap<String, String> mSystemSettingsMap;
@@ -459,6 +463,53 @@
     }
 
     @Test
+    public void testThrottlingOnBootFailures() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
+        for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+            noteBoot(i);
+        }
+        assertFalse(RescueParty.isAttemptingFactoryReset());
+    }
+
+    @Test
+    public void testThrottlingOnAppCrash() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
+        for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+            noteAppCrash(i + 1, true);
+        }
+        assertFalse(RescueParty.isAttemptingFactoryReset());
+    }
+
+    @Test
+    public void testNotThrottlingAfterTimeoutOnBootFailures() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
+        for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+            noteBoot(i);
+        }
+        assertTrue(RescueParty.isAttemptingFactoryReset());
+    }
+    @Test
+    public void testNotThrottlingAfterTimeoutOnAppCrash() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
+        for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+            noteAppCrash(i + 1, true);
+        }
+        assertTrue(RescueParty.isAttemptingFactoryReset());
+    }
+
+    @Test
     public void testNativeRescuePartyResets() {
         doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
         doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index b395f42..e7e26a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -1303,7 +1303,8 @@
         final BroadcastOptions actualOptions = new BroadcastOptions(actualOptionsBundle);
         assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
                 actualOptions.getDeliveryGroupPolicy());
-        assertTrue(actualOptions.isDeferUntilActive());
+        assertEquals(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE,
+                actualOptions.getDeferralPolicy());
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 9263bff..d56229c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -989,9 +989,16 @@
     private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
             int connectionGroup, int procState, long pss, long rss,
             String processName, String packageName) {
+        return makeProcessRecord(pid, uid, packageUid, definingUid, connectionGroup,
+                procState, pss, rss, processName, packageName, mAms);
+    }
+
+    static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            int connectionGroup, int procState, long pss, long rss,
+            String processName, String packageName, ActivityManagerService ams) {
         ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = packageName;
-        ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
+        ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
         app.setPid(pid);
         app.info.uid = packageUid;
         if (definingUid != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 01e2768..2b6f217 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -25,6 +25,8 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
+import static com.android.server.am.BroadcastRecord.calculateUrgent;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -38,6 +40,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
@@ -55,6 +58,7 @@
 import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -86,6 +90,15 @@
     private static final String[] PACKAGE_LIST = new String[] {PACKAGE1, PACKAGE2, PACKAGE3,
             PACKAGE4};
 
+    private static final int SYSTEM_UID = android.os.Process.SYSTEM_UID;
+    private static final int APP_UID = android.os.Process.FIRST_APPLICATION_UID;
+
+    private static final BroadcastOptions OPT_DEFAULT = BroadcastOptions.makeBasic();
+    private static final BroadcastOptions OPT_NONE = BroadcastOptions.makeBasic()
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+    private static final BroadcastOptions OPT_UNTIL_ACTIVE = BroadcastOptions.makeBasic()
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
     @Mock ActivityManagerInternal mActivityManagerInternal;
     @Mock BroadcastQueue mQueue;
     @Mock ProcessRecord mProcess;
@@ -213,6 +226,75 @@
     }
 
     @Test
+    public void testCalculateUrgent() {
+        final Intent intent = new Intent();
+        final Intent intentForeground = new Intent()
+                .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+        assertFalse(calculateUrgent(intent, null));
+        assertTrue(calculateUrgent(intentForeground, null));
+
+        {
+            final BroadcastOptions opts = BroadcastOptions.makeBasic();
+            assertFalse(calculateUrgent(intent, opts));
+        }
+        {
+            final BroadcastOptions opts = BroadcastOptions.makeBasic();
+            opts.setInteractive(true);
+            assertTrue(calculateUrgent(intent, opts));
+        }
+        {
+            final BroadcastOptions opts = BroadcastOptions.makeBasic();
+            opts.setAlarmBroadcast(true);
+            assertTrue(calculateUrgent(intent, opts));
+        }
+    }
+
+    @Test
+    public void testCalculateDeferUntilActive_App() {
+        // Verify non-urgent behavior
+        assertFalse(calculateDeferUntilActive(APP_UID, null, null, false, false));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_DEFAULT, null, false, false));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_NONE, null, false, false));
+        assertTrue(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, false, false));
+
+        // Verify urgent behavior
+        assertFalse(calculateDeferUntilActive(APP_UID, null, null, false, true));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_DEFAULT, null, false, true));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_NONE, null, false, true));
+        assertTrue(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, false, true));
+    }
+
+    @Test
+    public void testCalculateDeferUntilActive_System() {
+        BroadcastRecord.CORE_DEFER_UNTIL_ACTIVE = true;
+
+        // Verify non-urgent behavior
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, null, null, false, false));
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_DEFAULT, null, false, false));
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_NONE, null, false, false));
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_UNTIL_ACTIVE, null, false, false));
+
+        // Verify urgent behavior
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, null, null, false, true));
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_DEFAULT, null, false, true));
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_NONE, null, false, true));
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_UNTIL_ACTIVE, null, false, true));
+    }
+
+    @Test
+    public void testCalculateDeferUntilActive_Overrides() {
+        final IIntentReceiver resultTo = new IIntentReceiver.Default();
+
+        // Ordered broadcasts never deferred; requested option is ignored
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, true, false));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, resultTo, true, false));
+
+        // Unordered with result is always deferred; requested option is ignored
+        assertTrue(calculateDeferUntilActive(APP_UID, OPT_NONE, resultTo, false, false));
+    }
+
+    @Test
     public void testCleanupDisabledPackageReceivers() {
         final int user0 = UserHandle.USER_SYSTEM;
         final int user1 = user0 + 1;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
new file mode 100644
index 0000000..fd1b068
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+import static com.android.server.am.ApplicationExitInfoTest.makeProcessRecord;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.am.ApplicationExitInfoTest.ServiceThreadRule;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+
+/**
+ * Test class for the service timeout.
+ *
+ * Build/Install/Run:
+ *  atest ServiceTimeoutTest
+ */
+@Presubmit
+public final class ServiceTimeoutTest {
+    private static final String TAG = ServiceTimeoutTest.class.getSimpleName();
+    private static final long DEFAULT_SERVICE_TIMEOUT = 2000;
+
+    @Rule
+    public final ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
+    private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
+
+    private ActivityManagerService mAms;
+    private ProcessList mProcessList;
+    private ActiveServices mActiveServices;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mProcessList = spy(new ProcessList());
+
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+
+        final ActivityManagerService realAms = new ActivityManagerService(
+                new TestInjector(mContext), mServiceThreadRule.getThread());
+        realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+        realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
+        realAms.mPackageManagerInt = mPackageManagerInt;
+        realAms.mUsageStatsService = mUsageStatsManagerInt;
+        realAms.mProcessesReady = true;
+        realAms.mConstants.SERVICE_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+        realAms.mConstants.SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+        mAms = spy(realAms);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        mHandlerThread.quit();
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testServiceTimeoutAndProcessKill() throws Exception {
+        final int pid = 12345;
+        final int uid = 10123;
+        final String name = "com.example.foo";
+        final ProcessRecord app = makeProcessRecord(
+                pid,                   // pid
+                uid,                   // uid
+                uid,                   // packageUid
+                null,                  // definingUid
+                0,                     // connectionGroup
+                PROCESS_STATE_SERVICE, // procstate
+                0,                     // pss
+                0,                     // rss
+                name,                  // processName
+                name,                  // packageName
+                mAms);
+        app.makeActive(mock(IApplicationThread.class), mAms.mProcessStats);
+        mProcessList.updateLruProcessLocked(app, false, null);
+
+        final long now = SystemClock.uptimeMillis();
+        final ServiceRecord sr = spy(ServiceRecord.newEmptyInstanceForTest(mAms));
+        doNothing().when(sr).dump(any(), anyString());
+        sr.startRequested = true;
+        sr.executingStart = now;
+
+        app.mServices.startExecutingService(sr);
+        mActiveServices.scheduleServiceTimeoutLocked(app);
+
+        verify(mActiveServices, timeout(DEFAULT_SERVICE_TIMEOUT * 2).times(1))
+                .serviceTimeout(eq(app));
+
+        clearInvocations(mActiveServices);
+
+        app.mServices.startExecutingService(sr);
+        mActiveServices.scheduleServiceTimeoutLocked(app);
+
+        app.killLocked(TAG, 42, false);
+        mAms.removeLruProcessLocked(app);
+
+        verify(mActiveServices, after(DEFAULT_SERVICE_TIMEOUT * 4)
+                .times(1)).serviceTimeout(eq(app));
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandlerThread.getThreadHandler();
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mProcessList;
+        }
+
+        @Override
+        public ActiveServices getActiveServices(ActivityManagerService service) {
+            if (mActiveServices == null) {
+                mActiveServices = spy(new ActiveServices(service));
+            }
+            return mActiveServices;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 564893c..e7b3e6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -260,7 +260,7 @@
         mUms.setBootUser(OTHER_USER_ID);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
     }
 
     @Test
@@ -273,7 +273,8 @@
         mUms.setBootUser(PROFILE_USER_ID);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false))
+                .isEqualTo(UserHandle.USER_SYSTEM);
     }
 
     @Test
@@ -289,7 +290,7 @@
 
         // Boot user not switchable so return most recently in foreground.
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
     }
 
     @Test
@@ -299,7 +300,8 @@
         addUser(OTHER_USER_ID);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false))
+                .isEqualTo(UserHandle.USER_SYSTEM);
     }
 
     @Test
@@ -312,14 +314,15 @@
         setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
     }
 
     @Test
     public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
         setSystemUserHeadless(true);
 
-        assertThrows(UserManager.CheckedUserOperationException.class, () -> mUmi.getBootUser());
+        assertThrows(UserManager.CheckedUserOperationException.class,
+                () -> mUmi.getBootUser(/* waitUntilSet= */ false));
     }
 
     private void mockCurrentUser(@UserIdInt int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 239b6fd..70b5ac0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -596,6 +596,7 @@
                 .that(mMediator.assignUserToExtraDisplay(userId, displayId))
                 .isTrue();
         expectUserIsVisibleOnDisplay(userId, displayId);
+        expectDisplaysAssignedToUserContainsDisplayId(userId, displayId);
 
         if (unassign) {
             Log.d(TAG, "Calling unassignUserFromExtraDisplay(" + userId + ", " + displayId + ")");
@@ -603,6 +604,7 @@
                     .that(mMediator.unassignUserFromExtraDisplay(userId, displayId))
                     .isTrue();
             expectUserIsNotVisibleOnDisplay(userId, displayId);
+            expectDisplaysAssignedToUserDoesNotContainDisplayId(userId, displayId);
         }
     }
 
@@ -668,6 +670,7 @@
         expectUserIsNotVisibleOnDisplay(userId, INVALID_DISPLAY);
         expectUserIsNotVisibleOnDisplay(userId, SECONDARY_DISPLAY_ID);
         expectUserIsNotVisibleOnDisplay(userId, OTHER_SECONDARY_DISPLAY_ID);
+        expectDisplaysAssignedToUserIsEmpty(userId);
     }
 
     protected void expectDisplayAssignedToUser(@UserIdInt int userId, int displayId) {
@@ -680,6 +683,24 @@
                 .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(INVALID_DISPLAY);
     }
 
+    protected void expectDisplaysAssignedToUserContainsDisplayId(
+            @UserIdInt int userId, int displayId) {
+        expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplaysAssignedToUser(userId)).asList().contains(displayId);
+    }
+
+    protected void expectDisplaysAssignedToUserDoesNotContainDisplayId(
+            @UserIdInt int userId, int displayId) {
+        expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplaysAssignedToUser(userId)).asList()
+                .doesNotContain(displayId);
+    }
+
+    protected void expectDisplaysAssignedToUserIsEmpty(@UserIdInt int userId) {
+        expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplaysAssignedToUser(userId)).isNull();
+    }
+
     protected void expectUserCannotBeUnassignedFromDisplay(@UserIdInt int userId, int displayId) {
         expectWithMessage("unassignUserFromExtraDisplay(%s, %s)", userId, displayId)
                 .that(mMediator.unassignUserFromExtraDisplay(userId, displayId)).isFalse();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 7642e7b..6c6b608 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -28,7 +28,7 @@
 
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -109,7 +109,7 @@
                 device1Id).isNotEqualTo(device2Id);
 
 
-        int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+        int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
         assertWithMessage("InputManager's deviceIds list should contain id of device 1").that(
                 deviceIds).asList().contains(device1Id);
         assertWithMessage("InputManager's deviceIds list should contain id of device 2").that(
@@ -153,7 +153,7 @@
                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
 
         int deviceId = mInputController.getInputDeviceId(deviceToken);
-        int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+        int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
 
         assertWithMessage("InputManager's deviceIds list should contain id of the device").that(
             deviceIds).asList().contains(deviceId);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index d9d0715..64e6236 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -74,6 +74,8 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
 import javax.crypto.SecretKey;
@@ -324,16 +326,30 @@
         mInjected = mock(MockableRebootEscrowInjected.class);
         mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow,
                 mKeyStoreManager, mStorage, mInjected);
-        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage);
         HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
         thread.start();
         mHandler = new Handler(thread.getLooper());
+        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
+
     }
 
     private void setServerBasedRebootEscrowProvider() throws Exception {
         mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection,
                 mKeyStoreManager, mStorage, mInjected);
-        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage);
+        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
+    }
+
+    private void waitForHandler() throws InterruptedException {
+        // Wait for handler to complete processing.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(latch::countDown);
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+    }
+
+    private void callToRebootEscrowIfNeededAndWait(int userId) throws InterruptedException {
+        mService.callToRebootEscrowIfNeeded(userId, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        waitForHandler();
     }
 
     @Test
@@ -343,7 +359,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
     }
@@ -355,8 +371,7 @@
         mService.setRebootEscrowListener(mockListener);
         mService.prepareRebootEscrow();
 
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
-        verify(mockListener).onPreparedForReboot(eq(true));
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
         assertFalse(mStorage.hasRebootEscrowServerBlob());
     }
@@ -366,7 +381,7 @@
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
         mService.setRebootEscrowListener(mockListener);
         mService.prepareRebootEscrow();
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         clearInvocations(mRebootEscrow);
@@ -390,7 +405,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
 
@@ -414,7 +429,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -435,7 +450,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
 
@@ -453,10 +468,9 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
-        mService.callToRebootEscrowIfNeeded(SECURE_SECONDARY_USER_ID, FAKE_SP_VERSION,
-                FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
         verify(mRebootEscrow, never()).storeKey(any());
 
         assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
@@ -488,7 +502,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
 
@@ -511,7 +525,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -554,7 +568,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -598,7 +612,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -643,7 +657,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -689,7 +703,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -738,7 +752,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -791,7 +805,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -846,7 +860,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -893,7 +907,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -949,7 +963,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1008,7 +1022,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1068,7 +1082,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1124,7 +1138,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1176,7 +1190,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1207,7 +1221,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1235,7 +1249,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1274,7 +1288,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1309,7 +1323,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
         verify(mRebootEscrow, never()).storeKey(any());
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 6edef75..07b4345 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -34,6 +34,7 @@
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputManager;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.CombinedVibration;
 import android.os.Handler;
 import android.os.Process;
@@ -82,8 +83,8 @@
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
         InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
 
         when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
         doAnswer(invocation -> mIInputDevicesChangedListener = invocation.getArgument(0))
@@ -314,7 +315,7 @@
             deviceIdsAndGenerations[i + 1] = 2; // update by increasing it's generation to 2.
         }
         // Force initialization of mIInputDevicesChangedListener, if it still haven't
-        InputManager.getInstance().getInputDeviceIds();
+        InputManagerGlobal.getInstance().getInputDeviceIds();
         mIInputDevicesChangedListener.onInputDevicesChanged(deviceIdsAndGenerations);
         // Makes sure all callbacks from InputDeviceDelegate are executed.
         mTestLooper.dispatchAll();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
new file mode 100644
index 0000000..b94ed01
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
+import android.net.Uri;
+import android.os.IInterface;
+import android.service.notification.Condition;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ConditionProvidersTest extends UiServiceTestCase {
+
+    private ConditionProviders mProviders;
+
+    @Mock
+    private IPackageManager mIpm;
+    @Mock
+    private ManagedServices.UserProfiles mUserProfiles;
+    @Mock
+    private ConditionProviders.Callback mCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mProviders = new ConditionProviders(mContext, mUserProfiles, mIpm);
+        mProviders.setCallback(mCallback);
+    }
+
+    @Test
+    public void notifyConditions_findCondition() {
+        ComponentName cn = new ComponentName("package", "cls");
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), cn, 0, false, mock(ServiceConnection.class), 33, 100);
+        Condition[] conditions = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditions);
+
+        assertThat(mProviders.findCondition(cn, Uri.parse("a"))).isEqualTo(conditions[0]);
+        assertThat(mProviders.findCondition(cn, Uri.parse("b"))).isEqualTo(conditions[1]);
+        assertThat(mProviders.findCondition(null, Uri.parse("a"))).isNull();
+        assertThat(mProviders.findCondition(cn, null)).isNull();
+    }
+
+    @Test
+    public void notifyConditions_callbackOnConditionChanged() {
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+                mock(ServiceConnection.class), 33, 100);
+        Condition[] conditionsToNotify = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE),
+                new Condition(Uri.parse("c"), "summary3", Condition.STATE_TRUE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("c")), eq(conditionsToNotify[2]));
+        verifyNoMoreInteractions(mCallback);
+    }
+
+    @Test
+    public void notifyConditions_duplicateIds_ignored() {
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+                mock(ServiceConnection.class), 33, 100);
+        Condition[] conditionsToNotify = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE),
+                new Condition(Uri.parse("a"), "summary3", Condition.STATE_FALSE),
+                new Condition(Uri.parse("a"), "summary4", Condition.STATE_FALSE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
+
+        verifyNoMoreInteractions(mCallback);
+    }
+
+    @Test
+    public void notifyConditions_nullItems_ignored() {
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+                mock(ServiceConnection.class), 33, 100);
+        Condition[] conditionsToNotify = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                null,
+                null,
+                new Condition(Uri.parse("b"), "summary", Condition.STATE_TRUE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]));
+        verifyNoMoreInteractions(mCallback);
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
index d72cfc7..0564a73 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.notification;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -51,6 +53,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
+import com.android.server.notification.ValidateNotificationPeople.LookupResult;
+import com.android.server.notification.ValidateNotificationPeople.PeopleRankingReconsideration;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,6 +64,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -215,7 +220,7 @@
                 ContactsContract.Contacts.CONTENT_LOOKUP_URI,
                 ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX + contactId);
 
-        new ValidateNotificationPeople().searchContacts(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContacts(mockContext, lookupUri);
 
         ArgumentCaptor<Uri> queryUri = ArgumentCaptor.forClass(Uri.class);
         verify(mockContentResolver).query(
@@ -242,7 +247,7 @@
         final Uri lookupUri = Uri.withAppendedPath(
                 ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
 
-        new ValidateNotificationPeople().searchContacts(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContacts(mockContext, lookupUri);
 
         ArgumentCaptor<Uri> queryUri = ArgumentCaptor.forClass(Uri.class);
         verify(mockContentResolver).query(
@@ -277,7 +282,7 @@
 
         // call searchContacts and then mergePhoneNumbers, make sure we never actually
         // query the content resolver for a phone number
-        new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContactsAndLookupNumbers(mockContext, lookupUri);
         verify(mockContentResolver, never()).query(
                 eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
                 eq(ValidateNotificationPeople.PHONE_LOOKUP_PROJECTION),
@@ -320,7 +325,7 @@
 
         // call searchContacts and then mergePhoneNumbers, and check that we query
         // once for the
-        new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContactsAndLookupNumbers(mockContext, lookupUri);
         verify(mockContentResolver, times(1)).query(
                 eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
                 eq(ValidateNotificationPeople.PHONE_LOOKUP_PROJECTION),
@@ -339,7 +344,7 @@
 
         // Create validator with empty cache
         ValidateNotificationPeople vnp = new ValidateNotificationPeople();
-        LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
+        LruCache<String, LookupResult> cache = new LruCache<>(5);
         vnp.initForTests(mockContext, mockNotificationUsageStats, cache);
 
         NotificationRecord record = getNotificationRecord();
@@ -366,9 +371,8 @@
         float affinity = 0.7f;
 
         // Create a fake LookupResult for the data we'll pass in
-        LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
-        ValidateNotificationPeople.LookupResult lr =
-                mock(ValidateNotificationPeople.LookupResult.class);
+        LruCache<String, LookupResult> cache = new LruCache<>(5);
+        LookupResult lr = mock(LookupResult.class);
         when(lr.getAffinity()).thenReturn(affinity);
         when(lr.getPhoneNumbers()).thenReturn(new ArraySet<>(new String[]{lookupTel}));
         when(lr.isExpired()).thenReturn(false);
@@ -392,6 +396,23 @@
         assertTrue(record.getPhoneNumbers().contains(lookupTel));
     }
 
+    @Test
+    public void validatePeople_reconsiderationWillNotBeDelayed() {
+        final Context mockContext = mock(Context.class);
+        final ContentResolver mockContentResolver = mock(ContentResolver.class);
+        when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+        ValidateNotificationPeople vnp = new ValidateNotificationPeople();
+        vnp.initForTests(mockContext, mock(NotificationUsageStats.class), new LruCache<>(5));
+        NotificationRecord record = getNotificationRecord();
+        String[] callNumber = new String[]{"tel:12345678910"};
+        setNotificationPeople(record, callNumber);
+
+        RankingReconsideration rr = vnp.validatePeople(mockContext, record);
+
+        assertThat(rr).isNotNull();
+        assertThat(rr.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(0);
+    }
+
     // Creates a cursor that points to one item of Contacts data with the specified
     // columns.
     private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 06b6ed8..da078a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -459,8 +459,17 @@
         mainWindow.mInvGlobalScale = invGlobalScale;
         mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
 
+        doReturn(true).when(mActivity).isInLetterboxAnimation();
         assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
 
+        doReturn(false).when(mActivity).isInLetterboxAnimation();
+        assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+
+        doReturn(false).when(mainWindow).isOnScreen();
+        assertEquals(0, mController.getRoundedCornersRadius(mainWindow));
+
+        doReturn(true).when(mActivity).isInLetterboxAnimation();
+        assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
     }
 
     @Test
@@ -495,6 +504,7 @@
             insets.addSource(taskbar);
         }
         doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
+        doReturn(false).when(mActivity).isInLetterboxAnimation();
         doReturn(true).when(mActivity).isVisible();
         doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
         doReturn(insets).when(mainWindow).getInsetsState();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 753cc62..2cc46a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -51,6 +51,7 @@
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
@@ -3918,6 +3919,24 @@
         assertTrue(mActivity.inSizeCompatMode());
     }
 
+    @Test
+    public void testTopActivityInSizeCompatMode_pausedAndInSizeCompatMode_returnsTrue() {
+        setUpDisplaySizeWithApp(1000, 2500);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        spyOn(mActivity);
+        doReturn(mTask).when(mActivity).getOrganizedTask();
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+        mActivity.setState(PAUSED, "test");
+
+        assertTrue(mActivity.inSizeCompatMode());
+        assertEquals(mActivity.getState(), PAUSED);
+        assertTrue(mActivity.isVisible());
+        assertTrue(mTask.getTaskInfo().topActivityInSizeCompat);
+    }
+
     /**
      * Tests that all three paths in which aspect ratio logic can be applied yield the same
      * result, which is that aspect ratio is respected on app bounds. The three paths are
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index debfc84..616d528 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -40,6 +40,7 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.isIndependent;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
 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.spyOn;
@@ -54,6 +55,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -520,6 +522,47 @@
     }
 
     @Test
+    public void testCreateInfo_MultiDisplay() {
+        DisplayContent otherDisplay = createNewDisplay();
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task display0Task = createTask(mDisplayContent);
+        final Task display1Task = createTask(otherDisplay);
+        // Start states.
+        changes.put(display0Task,
+                new Transition.ChangeInfo(display0Task, false /* vis */, true /* exChg */));
+        changes.put(display1Task,
+                new Transition.ChangeInfo(display1Task, false /* vis */, true /* exChg */));
+        fillChangeMap(changes, display0Task);
+        fillChangeMap(changes, display1Task);
+        // End states.
+        display0Task.setVisibleRequested(true);
+        display1Task.setVisibleRequested(true);
+
+        final int transit = transition.mType;
+        int flags = 0;
+
+        participants.add(display0Task);
+        participants.add(display1Task);
+        ArrayList<Transition.ChangeInfo> targets =
+                Transition.calculateTargets(participants, changes);
+        TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+        assertEquals(2, info.getRootCount());
+        // Check that the changes are assigned to the correct display
+        assertEquals(mDisplayContent.getDisplayId(), info.getChange(
+                display0Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
+        assertEquals(otherDisplay.getDisplayId(), info.getChange(
+                display1Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
+        // Check that roots can be found by display and have the correct display
+        assertEquals(mDisplayContent.getDisplayId(),
+                info.getRoot(info.findRootIndex(mDisplayContent.getDisplayId())).getDisplayId());
+        assertEquals(otherDisplay.getDisplayId(),
+                info.getRoot(info.findRootIndex(otherDisplay.getDisplayId())).getDisplayId());
+    }
+
+    @Test
     public void testTargets_noIntermediatesToWallpaper() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
 
@@ -1350,7 +1393,7 @@
 
         verify(snapshotController, times(1)).recordSnapshot(eq(task2), eq(false));
 
-        openTransition.finishTransition();
+        controller.finishTransition(openTransition);
 
         // We are now going to simulate closing task1 to return back to (open) task2.
         final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
@@ -1359,7 +1402,13 @@
         closeTransition.collectExistenceChange(activity1);
         closeTransition.collectExistenceChange(task2);
         closeTransition.collectExistenceChange(activity2);
-        closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
+        closeTransition.setTransientLaunch(activity2, task1);
+        final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
+        assertNotNull(task1ChangeInfo);
+        assertTrue(task1ChangeInfo.hasChanged());
+        final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
+        assertNotNull(activity1ChangeInfo);
+        assertTrue(activity1ChangeInfo.hasChanged());
 
         activity1.setVisibleRequested(false);
         activity2.setVisibleRequested(true);
@@ -1375,9 +1424,27 @@
         verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false));
 
         enteringAnimReports.clear();
-        closeTransition.finishTransition();
+        doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
+                anyInt(), anyBoolean(), anyBoolean());
+        final boolean[] wasInFinishingTransition = { false };
+        controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
+            @Override
+            public void onAppTransitionFinishedLocked(IBinder token) {
+                final ActivityRecord r = ActivityRecord.forToken(token);
+                if (r != null) {
+                    wasInFinishingTransition[0] = controller.inFinishingTransition(r);
+                }
+            }
+        });
+        controller.finishTransition(closeTransition);
+        assertTrue(wasInFinishingTransition[0]);
+        assertNull(controller.mFinishingTransition);
 
+        assertTrue(activity2.isVisible());
         assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
+        // Because task1 is occluded by task2, finishTransition should make activity1 invisible.
+        assertFalse(activity1.isVisibleRequested());
+        assertFalse(activity1.isVisible());
         assertFalse(activity1.app.hasActivityInVisibleTask());
 
         verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 65631ea..984b868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -396,7 +396,7 @@
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         token.finishSync(t, false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
-        dc.mTransitionController.finishTransition(transit.getToken());
+        dc.mTransitionController.finishTransition(transit);
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index a68a573..17ad4e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1486,9 +1486,9 @@
         assertEquals(rootTask.mTaskId, info.taskId);
         assertTrue(info.topActivityInSizeCompat);
 
-        // Ensure task info show top activity that is not in foreground as not in size compat.
+        // Ensure task info show top activity that is not visible as not in size compat.
         clearInvocations(organizer);
-        doReturn(false).when(activity).isState(RESUMED);
+        doReturn(false).when(activity).isVisible();
         rootTask.onSizeCompatActivityChanged();
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         verify(organizer).onTaskInfoChanged(infoCaptor.capture());
@@ -1498,7 +1498,7 @@
 
         // Ensure task info show non size compat top activity as not in size compat.
         clearInvocations(organizer);
-        doReturn(true).when(activity).isState(RESUMED);
+        doReturn(true).when(activity).isVisible();
         doReturn(false).when(activity).inSizeCompatMode();
         rootTask.onSizeCompatActivityChanged();
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
index c2ee079..2a3c9bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
@@ -129,4 +131,14 @@
         assertEquals(uid2processes.size(), 1);
         assertEquals(mProcessMap.getProcess(FAKE_PID1), pid1uid2);
     }
+
+    @Test
+    public void testRemove_callsDestroy() {
+        var proc = spy(pid1uid1);
+        mProcessMap.put(FAKE_PID1, proc);
+
+        mProcessMap.remove(FAKE_PID1);
+
+        verify(proc).destroy();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d6cfd00..cf83981 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -159,6 +159,17 @@
     }
 
     @Test
+    public void testDestroy_unregistersDisplayAreaListener() {
+        final TestDisplayContent testDisplayContent1 = createTestDisplayContentInContainer();
+        final DisplayArea imeContainer1 = testDisplayContent1.getImeContainer();
+        mWpc.registerDisplayAreaConfigurationListener(imeContainer1);
+
+        mWpc.destroy();
+
+        assertNull(mWpc.getDisplayArea());
+    }
+
+    @Test
     public void testSetRunningRecentsAnimation() {
         mWpc.setRunningRecentsAnimation(true);
         mWpc.setRunningRecentsAnimation(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ce6cd90..b80500a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1799,7 +1799,7 @@
         }
 
         public void finish() {
-            mController.finishTransition(mLastTransit.getToken());
+            mController.finishTransition(mLastTransit);
         }
     }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index bf12b9c..212dc41 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1628,6 +1628,7 @@
      * <li>  9: WiFi Calling</li>
      * <li> 10: VoWifi</li>
      * <li> 11: %s WiFi Calling</li>
+     * <li> 12: WiFi Call</li>
      * @hide
      */
     public static final String KEY_WFC_SPN_FORMAT_IDX_INT = "wfc_spn_format_idx_int";
@@ -1974,8 +1975,13 @@
     /**
      * Boolean indicating if LTE+ icon should be shown if available.
      */
-    public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL =
-            "hide_lte_plus_data_icon_bool";
+    public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
+
+    /**
+     * Boolean indicting if the 5G slice icon should be shown if available.
+     * @hide
+     */
+    public static final String KEY_SHOW_5G_SLICE_ICON_BOOL = "show_5g_slice_icon_bool";
 
     /**
      * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
@@ -9913,6 +9919,7 @@
         sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
         sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
+        sDefaults.putBoolean(KEY_SHOW_5G_SLICE_ICON_BOOL, true);
         sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
         sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
         sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
index dd08085..1668193 100644
--- a/telephony/java/android/telephony/data/QosBearerSession.java
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -102,12 +102,11 @@
 
         QosBearerSession other = (QosBearerSession) o;
         return this.qosBearerSessionId == other.qosBearerSessionId
-                && this.qos.equals(other.qos)
+                && Objects.equals(this.qos, other.qos)
                 && this.qosBearerFilterList.size() == other.qosBearerFilterList.size()
                 && this.qosBearerFilterList.containsAll(other.qosBearerFilterList);
     }
 
-
     public static final @NonNull Parcelable.Creator<QosBearerSession> CREATOR =
             new Parcelable.Creator<QosBearerSession>() {
                 @Override
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
index 98221c9..cd9d81e 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
@@ -22,13 +22,6 @@
  */
 oneway interface ISatelliteStateCallback {
     /**
-     * Indicates that the satellite has pending datagrams for the device to be pulled.
-     *
-     * @param count Number of pending datagrams.
-     */
-    void onPendingDatagramCount(in int count);
-
-    /**
      * Indicates that the satellite modem state has changed.
      *
      * @param state The current satellite modem state.
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
similarity index 96%
rename from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
rename to telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index d3f1091..2442083 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -22,7 +22,7 @@
  * Interface for position update and datagram transfer state change callback.
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
+oneway interface ISatelliteTransmissionUpdateCallback {
     /**
      * Called when satellite datagram transfer state changed.
      *
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index a3c3f19..7c794473 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -30,31 +30,12 @@
     /** Satellite elevation in degrees */
     private float mSatelliteElevationDegrees;
 
-    /** Antenna azimuth in degrees */
-    private float mAntennaAzimuthDegrees;
-
-    /**
-     * Angle of rotation about the x axis. This value represents the angle between a plane
-     * parallel to the device's screen and a plane parallel to the ground.
-     */
-    private float mAntennaPitchDegrees;
-
-    /**
-     * Angle of rotation about the y axis. This value represents the angle between a plane
-     * perpendicular to the device's screen and a plane parallel to the ground.
-     */
-    private float mAntennaRollDegrees;
-
     /**
      * @hide
      */
-    public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees,
-            float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
+    public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) {
         mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
         mSatelliteElevationDegrees = satelliteElevationDegrees;
-        mAntennaAzimuthDegrees = antennaAzimuthDegrees;
-        mAntennaPitchDegrees = antennaPitchDegrees;
-        mAntennaRollDegrees = antennaRollDegrees;
     }
 
     private PointingInfo(Parcel in) {
@@ -70,9 +51,6 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeFloat(mSatelliteAzimuthDegrees);
         out.writeFloat(mSatelliteElevationDegrees);
-        out.writeFloat(mAntennaAzimuthDegrees);
-        out.writeFloat(mAntennaPitchDegrees);
-        out.writeFloat(mAntennaRollDegrees);
     }
 
     public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
@@ -99,18 +77,6 @@
 
         sb.append("SatelliteElevationDegrees:");
         sb.append(mSatelliteElevationDegrees);
-        sb.append(",");
-
-        sb.append("AntennaAzimuthDegrees:");
-        sb.append(mAntennaAzimuthDegrees);
-        sb.append(",");
-
-        sb.append("AntennaPitchDegrees:");
-        sb.append(mAntennaPitchDegrees);
-        sb.append(",");
-
-        sb.append("AntennaRollDegrees:");
-        sb.append(mAntennaRollDegrees);
         return sb.toString();
     }
 
@@ -122,23 +88,8 @@
         return mSatelliteElevationDegrees;
     }
 
-    public float getAntennaAzimuthDegrees() {
-        return mAntennaAzimuthDegrees;
-    }
-
-    public float getAntennaPitchDegrees() {
-        return mAntennaPitchDegrees;
-    }
-
-    public float getAntennaRollDegrees() {
-        return mAntennaRollDegrees;
-    }
-
     private void readFromParcel(Parcel in) {
         mSatelliteAzimuthDegrees = in.readFloat();
         mSatelliteElevationDegrees = in.readFloat();
-        mAntennaAzimuthDegrees = in.readFloat();
-        mAntennaPitchDegrees = in.readFloat();
-        mAntennaRollDegrees = in.readFloat();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 889856b..df80159 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -33,31 +33,24 @@
     @NonNull @SatelliteManager.NTRadioTechnology private Set<Integer> mSupportedRadioTechnologies;
 
     /**
-     * Whether satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     */
-    private boolean mIsAlwaysOn;
-
-    /**
      * Whether UE needs to point to a satellite to send and receive data.
      */
-    private boolean mNeedsPointingToSatellite;
+    private boolean mIsPointingRequired;
 
     /**
-     * Whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      */
-    private boolean mNeedsSeparateSimProfile;
+    private int mMaxBytesPerOutgoingDatagram;
 
     /**
      * @hide
      */
-    public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
-            boolean needsPointingToSatellite, boolean needsSeparateSimProfile) {
+    public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
+            boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
         mSupportedRadioTechnologies = supportedRadioTechnologies == null
                 ? new HashSet<>() : supportedRadioTechnologies;
-        mIsAlwaysOn = isAlwaysOn;
-        mNeedsPointingToSatellite = needsPointingToSatellite;
-        mNeedsSeparateSimProfile = needsSeparateSimProfile;
+        mIsPointingRequired = isPointingRequired;
+        mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
     }
 
     private SatelliteCapabilities(Parcel in) {
@@ -80,9 +73,8 @@
             out.writeInt(0);
         }
 
-        out.writeBoolean(mIsAlwaysOn);
-        out.writeBoolean(mNeedsPointingToSatellite);
-        out.writeBoolean(mNeedsSeparateSimProfile);
+        out.writeBoolean(mIsPointingRequired);
+        out.writeInt(mMaxBytesPerOutgoingDatagram);
     }
 
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -111,16 +103,12 @@
             sb.append("none,");
         }
 
-        sb.append("isAlwaysOn:");
-        sb.append(mIsAlwaysOn);
+        sb.append("isPointingRequired:");
+        sb.append(mIsPointingRequired);
         sb.append(",");
 
-        sb.append("needsPointingToSatellite:");
-        sb.append(mNeedsPointingToSatellite);
-        sb.append(",");
-
-        sb.append("needsSeparateSimProfile:");
-        sb.append(mNeedsSeparateSimProfile);
+        sb.append("maxBytesPerOutgoingDatagram");
+        sb.append(mMaxBytesPerOutgoingDatagram);
         return sb.toString();
     }
 
@@ -133,33 +121,22 @@
     }
 
     /**
-     * Get whether the satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     *
-     * @return {@code true} if the satellite modem is always on and {@code false} otherwise.
-     */
-    public boolean isAlwaysOn() {
-        return mIsAlwaysOn;
-    }
-
-    /**
      * Get whether UE needs to point to a satellite to send and receive data.
      *
-     * @return {@code true} if UE needs to pointing to a satellite to send and receive data and
+     * @return {@code true} if UE needs to point to a satellite to send and receive data and
      *         {@code false} otherwise.
      */
-    public boolean needsPointingToSatellite() {
-        return mNeedsPointingToSatellite;
+    public boolean isPointingRequired() {
+        return mIsPointingRequired;
     }
 
     /**
-     * Get whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      *
-     * @return {@code true} if UE needs a separate SIM profile to comunicate with the satellite
-     *         network and {@code false} otherwise.
+     * @return The maximum number of bytes per datagram that can be sent over satellite.
      */
-    public boolean needsSeparateSimProfile() {
-        return mNeedsSeparateSimProfile;
+    public int getMaxBytesPerOutgoingDatagram() {
+        return mMaxBytesPerOutgoingDatagram;
     }
 
     private void readFromParcel(Parcel in) {
@@ -171,8 +148,7 @@
             }
         }
 
-        mIsAlwaysOn = in.readBoolean();
-        mNeedsPointingToSatellite = in.readBoolean();
-        mNeedsSeparateSimProfile = in.readBoolean();
+        mIsPointingRequired = in.readBoolean();
+        mMaxBytesPerOutgoingDatagram = in.readInt();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index cc5a9f4..d3cb8a0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -17,7 +17,6 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,7 +27,7 @@
     /**
      * Datagram to be sent or received over satellite.
      */
-    private byte[] mData;
+    @NonNull private byte[] mData;
 
     /**
      * @hide
@@ -51,8 +50,8 @@
         out.writeByteArray(mData);
     }
 
-    public static final @android.annotation.NonNull Creator<SatelliteDatagram> CREATOR =
-            new Creator<SatelliteDatagram>() {
+    @NonNull public static final Creator<SatelliteDatagram> CREATOR =
+            new Creator<>() {
                 @Override
                 public SatelliteDatagram createFromParcel(Parcel in) {
                     return new SatelliteDatagram(in);
@@ -64,8 +63,7 @@
                 }
             };
 
-    @Nullable
-    public byte[] getSatelliteDatagram() {
+    @NonNull public byte[] getSatelliteDatagram() {
         return mData;
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index 213b985..f237ada 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -17,48 +17,18 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.os.Binder;
 
 import com.android.internal.telephony.ILongConsumer;
 
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for listening to satellite datagrams.
  *
  * @hide
  */
-public class SatelliteDatagramCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteDatagramCallback.Stub {
-        private final SatelliteDatagramCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteDatagramCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteDatagramReceived(long datagramId,
-                @NonNull SatelliteDatagram datagram, int pendingCount,
-                @NonNull ILongConsumer callback) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() -> mLocalCallback.onSatelliteDatagramReceived(datagramId,
-                        datagram, pendingCount, callback));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteDatagramCallback {
     /**
      * Called when there is an incoming datagram to be received.
+     *
      * @param datagramId An id that uniquely identifies incoming datagram.
      * @param datagram Datagram to be received over satellite.
      * @param pendingCount Number of datagrams yet to be received by the app.
@@ -66,19 +36,6 @@
      *                 datagramId to Telephony. If the callback is not received within five minutes,
      *                 Telephony will resend the datagram.
      */
-    public void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
-            int pendingCount, @NonNull ILongConsumer callback) {
-        // Base Implementation
-    }
-
-    /** @hide */
-    @NonNull
-    final ISatelliteDatagramCallback getBinder() {
-        return mBinder;
-    }
-
-    /** @hide */
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
+            int pendingCount, @NonNull ILongConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 9ec6929..d0abfbf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -36,12 +36,15 @@
 import android.telephony.TelephonyFrameworkInitializer;
 
 import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ILongConsumer;
 import com.android.internal.telephony.ITelephony;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -55,6 +58,17 @@
 public class SatelliteManager {
     private static final String TAG = "SatelliteManager";
 
+    private static final ConcurrentHashMap<SatelliteDatagramCallback, ISatelliteDatagramCallback>
+            sSatelliteDatagramCallbackMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteProvisionStateCallback,
+            ISatelliteProvisionStateCallback> sSatelliteProvisionStateCallbackMap =
+            new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteStateCallback, ISatelliteStateCallback>
+            sSatelliteStateCallbackMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
+            ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
+            new ConcurrentHashMap<>();
+
     private final int mSubId;
 
     /**
@@ -66,6 +80,7 @@
      * 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);
@@ -116,7 +131,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsSatelliteDemoModeEnabled(Executor, OutcomeReceiver)}.
+     * {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}.
      * @hide
      */
     public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
@@ -137,14 +152,6 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestMaxSizePerSendingDatagram(Executor, OutcomeReceiver)} .
-     * @hide
-     */
-    public static final String KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT =
-            "max_characters_per_satellite_text";
-
-    /**
-     * Bundle key to get the response from
      * {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -319,23 +326,25 @@
     public @interface NTRadioTechnology {}
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled, this
-     * will also disable the cellular modem, and if the satellite modem is disabled, this will also
-     * re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+     * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable {@code true} to enable the satellite modem and {@code false} to disable.
+     * @param enableSatellite {@code true} to enable the satellite modem and
+     *                        {@code false} to disable.
+     * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @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.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteEnabled(
-            boolean enable, @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+            @NonNull @CallbackExecutor Executor executor,
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         try {
             ITelephony telephony = getITelephony();
@@ -344,10 +353,11 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                telephony.requestSatelliteEnabled(mSubId, enable, errorCallback);
+                telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode,
+                        errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -412,50 +422,13 @@
     }
 
     /**
-     * Request to enable or disable the satellite service demo mode.
-     *
-     * @param enable {@code true} to enable the satellite demo mode and {@code false} to disable.
-     * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener 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.
-     */
-    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteDemoModeEnabled(boolean enable,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
-
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
-                    }
-                };
-                telephony.requestSatelliteDemoModeEnabled(mSubId, enable, errorCallback);
-            } else {
-                throw new IllegalStateException("telephony service is null.");
-            }
-        } catch (RemoteException ex) {
-            Rlog.e(TAG, "requestSatelliteDemoModeEnabled() RemoteException: ", ex);
-            ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Request to get whether the satellite service demo mode is enabled.
      *
      * @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)}
-     *                 will return a {@code boolean} with value {@code true} if the satellite
-     *                 demo mode is enabled and {@code false} otherwise.
+     *                 will return a {@code boolean} with value {@code true} if demo mode is enabled
+     *                 and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
      *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
      *
@@ -463,7 +436,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestIsSatelliteDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
+    public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -492,12 +465,12 @@
                         }
                     }
                 };
-                telephony.requestIsSatelliteDemoModeEnabled(mSubId, receiver);
+                telephony.requestIsDemoModeEnabled(mSubId, receiver);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("requestIsSatelliteDemoModeEnabled() RemoteException: " + ex);
+            loge("requestIsDemoModeEnabled() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
@@ -742,145 +715,117 @@
     public @interface DatagramType {}
 
     /**
-     * Start receiving satellite position updates.
+     * Start receiving satellite transmission updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
-     * Satellite position updates are started only on {@link #SATELLITE_ERROR_NONE}.
+     * Satellite transmission updates are started only on {@link #SATELLITE_ERROR_NONE}.
      * All other results indicate that this operation failed.
-     * Once satellite position updates begin, datagram transfer state updates will be sent
-     * through {@link SatellitePositionUpdateCallback}.
+     * Once satellite transmission updates begin, position and datagram transfer state updates
+     * will be sent through {@link SatelliteTransmissionUpdateCallback}.
      *
      * @param executor The executor on which the callback and error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
-     * @param callback The callback to notify of changes in satellite position.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param callback The callback to notify of satellite transmission updates.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void startSatellitePositionUpdates(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener,
-            @NonNull SatellitePositionUpdateCallback callback) {
+    public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
+            @SatelliteError @NonNull Consumer<Integer> resultListener,
+            @NonNull SatelliteTransmissionUpdateCallback callback) {
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
         Objects.requireNonNull(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
                 IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                telephony.startSatellitePositionUpdates(
-                        mSubId, errorCallback, callback.getBinder());
+                ISatelliteTransmissionUpdateCallback internalCallback =
+                        new ISatelliteTransmissionUpdateCallback.Stub() {
+                            @Override
+                            public void onDatagramTransferStateChanged(int state,
+                                    int sendPendingCount, int receivePendingCount, int errorCode) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onDatagramTransferStateChanged(
+                                                state, sendPendingCount, receivePendingCount,
+                                                errorCode)));
+                            }
+
+                            @Override
+                            public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatellitePositionChanged(pointingInfo)));
+                            }
+                        };
+                sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
+                telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
+                        internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("startSatellitePositionUpdates() RemoteException: " + ex);
+            loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Stop receiving satellite position updates.
+     * Stop receiving satellite transmission updates.
      * This can be called by the pointing UI when the user stops pointing to the satellite.
-     * Satellite position updates are stopped and the callback is unregistered only on
+     * Satellite transmission updates are stopped and the callback is unregistered only on
      * {@link #SATELLITE_ERROR_NONE}. All other results that this operation failed.
      *
-     * @param callback The callback that was passed to
-     * {@link #startSatellitePositionUpdates(Executor, Consumer, SatellitePositionUpdateCallback)}.
+     * @param callback The callback that was passed to {@link
+     * #startSatelliteTransmissionUpdates(Executor, Consumer, SatelliteTransmissionUpdateCallback)}.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @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.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void stopSatellitePositionUpdates(@NonNull SatellitePositionUpdateCallback callback,
+    public void stopSatelliteTransmissionUpdates(
+            @NonNull SatelliteTransmissionUpdateCallback callback,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(callback);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
+        ISatelliteTransmissionUpdateCallback internalCallback =
+                sSatelliteTransmissionUpdateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
-                    }
-                };
-                telephony.stopSatellitePositionUpdates(mSubId, errorCallback,
-                        callback.getBinder());
-                // TODO: Notify SmsHandler that pointing UI stopped
-            } else {
-                throw new IllegalStateException("telephony service is null.");
-            }
-        } catch (RemoteException ex) {
-            loge("stopSatellitePositionUpdates() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request to get the maximum number of bytes per datagram that can be sent to satellite.
-     *
-     * @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)}
-     *                 will return the maximum number of bytes per datagram that can be sent to
-     *                 satellite.
-     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *
-     * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
-     */
-    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestMaxSizePerSendingDatagram(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                ResultReceiver receiver = new ResultReceiver(null) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
-                            if (resultData.containsKey(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT)) {
-                                int maxCharacters =
-                                        resultData.getInt(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(maxCharacters)));
-                            } else {
-                                loge("KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT does not exist.");
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
-                            }
-                        } else {
-                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                    callback.onError(new SatelliteException(resultCode))));
+                if (internalCallback != null) {
+                    IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(
+                                    () -> resultListener.accept(result)));
                         }
-                    }
-                };
-                telephony.requestMaxSizePerSendingDatagram(mSubId, receiver);
+                    };
+                    telephony.stopSatelliteTransmissionUpdates(mSubId, errorCallback,
+                            internalCallback);
+                    // TODO: Notify SmsHandler that pointing UI stopped
+                } else {
+                    loge("stopSatelliteTransmissionUpdates: No internal callback.");
+                    executor.execute(() -> Binder.withCleanCallingIdentity(
+                            () -> resultListener.accept(SATELLITE_INVALID_ARGUMENTS)));
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("requestMaxCharactersPerSatelliteTextMessage() RemoteException: " + ex);
+            loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
@@ -891,23 +836,24 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
+     * @param regionId The region ID for the device's current location.
      * @param cancellationSignal The optional signal used by the caller to cancel the provision
      *                           request. Even when the cancellation is signaled, Telephony will
      *                           still trigger the callback to return the result of this request.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @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.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void provisionSatelliteService(@NonNull String token,
+    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         ICancellationSignal cancelRemote = null;
         try {
@@ -917,10 +863,11 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                cancelRemote = telephony.provisionSatelliteService(mSubId, token, errorCallback);
+                cancelRemote = telephony.provisionSatelliteService(mSubId, token, regionId,
+                        errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -942,7 +889,7 @@
      * {@link #provisionSatelliteService(String, CancellationSignal, Executor, Consumer)}.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @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.
@@ -950,10 +897,10 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void deprovisionSatelliteService(@NonNull String token,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         try {
             ITelephony telephony = getITelephony();
@@ -962,7 +909,7 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
                 telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
@@ -996,9 +943,18 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
+                ISatelliteProvisionStateCallback internalCallback =
+                        new ISatelliteProvisionStateCallback.Stub() {
+                            @Override
+                            public void onSatelliteProvisionStateChanged(boolean provisioned) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteProvisionStateChanged(
+                                                provisioned)));
+                            }
+                        };
+                sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
                 return telephony.registerForSatelliteProvisionStateChanged(
-                        mSubId, callback.getBinder());
+                        mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1023,11 +979,17 @@
     public void unregisterForSatelliteProvisionStateChanged(
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteProvisionStateCallback internalCallback =
+                sSatelliteProvisionStateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteProvisionStateChanged(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteProvisionStateChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteProvisionStateChanged: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1112,9 +1074,15 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
-                return telephony.registerForSatelliteModemStateChanged(mSubId,
-                        callback.getBinder());
+                ISatelliteStateCallback internalCallback = new ISatelliteStateCallback.Stub() {
+                    @Override
+                    public void onSatelliteModemStateChanged(int state) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                callback.onSatelliteModemStateChanged(state)));
+                    }
+                };
+                sSatelliteStateCallbackMap.put(callback, internalCallback);
+                return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1138,11 +1106,16 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteModemStateChanged(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteModemStateChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteModemStateChanged: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1155,8 +1128,6 @@
     /**
      * Register to receive incoming datagrams over satellite.
      *
-     * @param datagramType datagram type indicating whether the datagram is of type
-     *                     SOS_SMS or LOCATION_SHARING.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle incoming datagrams over satellite.
      *                 This callback with be invoked when a new datagram is received from satellite.
@@ -1167,7 +1138,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @SatelliteError public int registerForSatelliteDatagram(@DatagramType int datagramType,
+    @SatelliteError public int registerForSatelliteDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(executor);
@@ -1176,9 +1147,19 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
-                return telephony.registerForSatelliteDatagram(mSubId, datagramType,
-                            callback.getBinder());
+                ISatelliteDatagramCallback internalCallback =
+                        new ISatelliteDatagramCallback.Stub() {
+                            @Override
+                            public void onSatelliteDatagramReceived(long datagramId,
+                                    @NonNull SatelliteDatagram datagram, int pendingCount,
+                                    @NonNull ILongConsumer ack) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteDatagramReceived(
+                                                datagramId, datagram, pendingCount, ack)));
+                            }
+                        };
+                sSatelliteDatagramCallbackMap.put(callback, internalCallback);
+                return telephony.registerForSatelliteDatagram(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1194,7 +1175,7 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteDatagram(int, Executor, SatelliteDatagramCallback)}.
+     * {@link #registerForSatelliteDatagram(Executor, SatelliteDatagramCallback)}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1202,11 +1183,17 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteDatagramCallback internalCallback =
+                sSatelliteDatagramCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteDatagram(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteDatagram(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteDatagram: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1222,7 +1209,7 @@
      * This method requests modem to check if there are any pending datagrams to be received over
      * satellite. If there are any incoming datagrams, they will be received via
      * {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int,
-     *          ISatelliteDatagramReceiverAck)}
+     *        ILongConsumer)}
      *
      * @param executor The executor on which the result listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
@@ -1371,9 +1358,8 @@
     }
 
     /**
-     * Request to get the time after which the satellite will be visible. This is an
-     * {@code int} representing the duration in seconds after which the satellite will be visible.
-     * This will return {@code 0} if the satellite is currently visible.
+     * Request to get the duration in seconds after which the satellite will be visible.
+     * This will be {@link Duration#ZERO} if the satellite is currently visible.
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
@@ -1387,7 +1373,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
+            @NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -1402,7 +1388,8 @@
                                 int nextVisibilityDuration =
                                         resultData.getInt(KEY_SATELLITE_NEXT_VISIBILITY);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(nextVisibilityDuration)));
+                                        callback.onResult(
+                                                Duration.ofSeconds(nextVisibilityDuration))));
                             } else {
                                 loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
diff --git a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
deleted file mode 100644
index d44a84d..0000000
--- a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.satellite;
-
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
-/**
- * A callback class for monitoring satellite position update and datagram transfer state change
- * events.
- *
- * @hide
- */
-public class SatellitePositionUpdateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatellitePositionUpdateCallback.Stub {
-        private final SatellitePositionUpdateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatellitePositionUpdateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatellitePositionChanged(pointingInfo));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        @Override
-        public void onDatagramTransferStateChanged(
-                @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-                int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onDatagramTransferStateChanged(
-                                state, sendPendingCount, receivePendingCount, errorCode));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
-    /**
-     * Called when the satellite position changed.
-     *
-     * @param pointingInfo The pointing info containing the satellite location.
-     */
-    public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
-        // Base Implementation
-    }
-
-    /**
-     * Called when satellite datagram transfer state changed.
-     *
-     * @param state The new datagram transfer state.
-     * @param sendPendingCount The number of datagrams that are currently being sent.
-     * @param receivePendingCount The number of datagrams that are currently being received.
-     * @param errorCode If datagram transfer failed, the reason for failure.
-     */
-    public void onDatagramTransferStateChanged(
-            @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-            int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
-        // Base Implementation
-    }
-
-    /**@hide*/
-    @NonNull
-    final ISatellitePositionUpdateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
-}
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 2b6a5d9..a62eb8b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,61 +16,17 @@
 
 package android.telephony.satellite;
 
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for monitoring satellite provision state change events.
  *
  * @hide
  */
-public class SatelliteProvisionStateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteProvisionStateCallback.Stub {
-        private final SatelliteProvisionStateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteProvisionStateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteProvisionStateChanged(boolean provisioned) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatelliteProvisionStateChanged(provisioned));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteProvisionStateCallback {
     /**
      * Called when satellite provision state changes.
      *
      * @param provisioned The new provision state. {@code true} means satellite is provisioned
      *                    {@code false} means satellite is not provisioned.
      */
-    public void onSatelliteProvisionStateChanged(boolean provisioned) {
-        // Base Implementation
-    }
-
-    /**@hide*/
-    @NonNull
-    final ISatelliteProvisionStateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteProvisionStateChanged(boolean provisioned);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 17d05b7..d9ecaa3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,80 +16,15 @@
 
 package android.telephony.satellite;
 
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for monitoring satellite modem state change events.
  *
  * @hide
  */
-public class SatelliteStateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteStateCallback.Stub {
-        private final SatelliteStateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteStateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatelliteModemStateChanged(state));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        @Override
-        public void onPendingDatagramCount(int count) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onPendingDatagramCount(count));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteStateCallback {
     /**
      * Called when satellite modem state changes.
      * @param state The new satellite modem state.
      */
-    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
-        // Base Implementation
-    }
-
-    /**
-     * Called when there are pending datagrams to be received from satellite.
-     * @param count Pending datagram count.
-     */
-    public void onPendingDatagramCount(int count) {
-        // Base Implementation
-    }
-
-    //TODO: Add an API for datagram transfer state update here.
-
-    /**@hide*/
-    @NonNull
-    final ISatelliteStateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
 }
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
similarity index 68%
copy from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
copy to telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index d3f1091..0efbd1f 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -16,13 +16,22 @@
 
 package android.telephony.satellite;
 
-import android.telephony.satellite.PointingInfo;
+import android.annotation.NonNull;
 
 /**
- * Interface for position update and datagram transfer state change callback.
+ * A callback class for monitoring satellite position update and datagram transfer state change
+ * events.
+ *
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
+public interface SatelliteTransmissionUpdateCallback {
+    /**
+     * Called when the satellite position changed.
+     *
+     * @param pointingInfo The pointing info containing the satellite location.
+     */
+    void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
+
     /**
      * Called when satellite datagram transfer state changed.
      *
@@ -31,13 +40,7 @@
      * @param receivePendingCount The number of datagrams that are currently being received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onDatagramTransferStateChanged(in int state, in int sendPendingCount,
-            in int receivePendingCount, in int errorCode);
-
-    /**
-     * Called when the satellite position changed.
-     *
-     * @param pointingInfo The pointing info containing the satellite location.
-     */
-    void onSatellitePositionChanged(in PointingInfo pointingInfo);
+    void onDatagramTransferStateChanged(@SatelliteManager.SatelliteDatagramTransferState int state,
+            int sendPendingCount, int receivePendingCount,
+            @SatelliteManager.SatelliteError int errorCode);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index d93ee21..a780cb9 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -49,10 +49,9 @@
      * Listening mode allows the satellite service to listen for incoming pages.
      *
      * @param enable True to enable satellite listening mode and false to disable.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -64,16 +63,17 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, in int timeout,
-            in IIntegerConsumer errorCallback);
+    void requestSatelliteListeningEnabled(in boolean enable, in int timeout,
+            in IIntegerConsumer resultCallback);
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled,
-     * this will also disable the cellular modem, and if the satellite modem is disabled,
-     * this will also re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem
+     * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable True to enable the satellite modem and false to disable.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True to enable demo mode and false to disable.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -85,13 +85,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteEnabled(in boolean enabled, in IIntegerConsumer errorCallback);
+    void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Request to get whether the satellite modem is enabled.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether the satellite modem is enabled.
      *
@@ -105,13 +106,13 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteEnabled(in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+    void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
 
     /**
      * Request to get whether the satellite service is supported on the device.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether the satellite service is supported on the device.
      *
@@ -125,14 +126,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteSupported(in IIntegerConsumer errorCallback,
+    void requestIsSatelliteSupported(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
 
     /**
      * Request to get the SatelliteCapabilities of the satellite service.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the SatelliteCapabilities of the satellite service.
      *
@@ -146,7 +147,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteCapabilities(in IIntegerConsumer errorCallback,
+    void requestSatelliteCapabilities(in IIntegerConsumer resultCallback,
             in ISatelliteCapabilitiesConsumer callback);
 
     /**
@@ -154,7 +155,7 @@
      * The satellite service should report the satellite pointing info via
      * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -166,13 +167,13 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void startSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
+    void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
     /**
      * User stopped pointing to the satellite.
      * The satellite service should stop reporting satellite pointing info to the framework.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -184,28 +185,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void stopSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
-
-    /**
-     * Request to get the maximum number of characters per MO text message on satellite.
-     *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the maximum number of characters per MO text message on satellite.
-     *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     */
-    void requestMaxCharactersPerMOTextMessage(in IIntegerConsumer errorCallback,
-            in IIntegerConsumer callback);
+    void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
     /**
      * Provision the device with a satellite provider.
@@ -214,7 +194,8 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param regionId The region ID for the device's current location.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -229,7 +210,8 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    void provisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+    void provisionSatelliteService(in String token, in String regionId,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Deprovision the device with the satellite provider.
@@ -237,7 +219,7 @@
      * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -252,13 +234,13 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    void deprovisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+    void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
 
     /**
      * Request to get whether this device is provisioned with a satellite provider.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether this device is provisioned with a satellite provider.
      *
@@ -272,7 +254,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteProvisioned(in IIntegerConsumer errorCallback,
+    void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
 
     /**
@@ -280,7 +262,7 @@
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -297,15 +279,14 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    void pollPendingSatelliteDatagrams(in IIntegerConsumer errorCallback);
+    void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
 
     /**
      * Send datagram over satellite.
      *
      * @param datagram Datagram to send in byte format.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param isEmergency Whether this is an emergency datagram.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -323,16 +304,16 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isDemoMode,
-            in boolean isEmergency, in IIntegerConsumer errorCallback);
+    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Request the current satellite modem state.
      * The satellite service should report the current satellite modem state via
      * ISatelliteListener#onSatelliteModemStateChanged.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the current satellite modem state.
      *
@@ -346,14 +327,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteModemState(in IIntegerConsumer errorCallback,
+    void requestSatelliteModemState(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
 
     /**
      * Request to get whether satellite communication is allowed for the current location.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether satellite communication is allowed for the current location.
      *
@@ -368,15 +349,15 @@
      *   SatelliteError:NO_RESOURCES
      */
     void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-            in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+            in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
 
     /**
      * Request to get the time after which the satellite will be visible. This is an int
      * representing the duration in seconds after which the satellite will be visible.
      * This will return 0 if the satellite is currently visible.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the time after which the satellite will be visible.
      *
@@ -390,6 +371,6 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestTimeForNextSatelliteVisibility(in IIntegerConsumer errorCallback,
+    void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d966868..5e69215 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -43,10 +43,8 @@
 
     /**
      * Indicates that the satellite has pending datagrams for the device to be pulled.
-     *
-     * @param count Number of pending datagrams.
      */
-    void onPendingDatagramCount(in int count);
+    void onPendingDatagrams();
 
     /**
      * Indicates that the satellite pointing input has changed.
@@ -61,11 +59,4 @@
      * @param state The current satellite modem state.
      */
     void onSatelliteModemStateChanged(in SatelliteModemState state);
-
-    /**
-     * Indicates that the satellite radio technology has changed.
-     *
-     * @param technology The current satellite radio technology.
-     */
-    void onSatelliteRadioTechnologyChanged(in NTRadioTechnology technology);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
index 83392dd..52a36d8 100644
--- a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
+++ b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
@@ -29,21 +29,4 @@
      * Satellite elevation in degrees.
      */
     float satelliteElevation;
-
-    /**
-     * Antenna azimuth in degrees.
-     */
-    float antennaAzimuth;
-
-    /**
-     * Angle of rotation about the x axis. This value represents the angle between a plane
-     * parallel to the device's screen and a plane parallel to the ground.
-     */
-    float antennaPitch;
-
-    /**
-     * Angle of rotation about the y axis. This value represents the angle between a plane
-     * perpendicular to the device's screen and a plane parallel to the ground.
-     */
-    float antennaRoll;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index 10c2ea3..cd69da1 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -28,18 +28,12 @@
     NTRadioTechnology[] supportedRadioTechnologies;
 
     /**
-     * Whether satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     */
-    boolean isAlwaysOn;
-
-    /**
      * Whether UE needs to point to a satellite to send and receive data.
      */
-    boolean needsPointingToSatellite;
+    boolean isPointingRequired;
 
     /**
-     * Whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      */
-    boolean needsSeparateSimProfile;
+    int maxBytesPerOutgoingDatagram;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 711dcbe..debb394 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -70,20 +70,21 @@
         }
 
         @Override
-        public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
-                int timeout, IIntegerConsumer errorCallback) throws RemoteException {
+        public void requestSatelliteListeningEnabled(boolean enable, int timeout,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteListeningEnabled(
-                                    enable, isDemoMode, timeout, errorCallback),
+                            .requestSatelliteListeningEnabled(enable, timeout, errorCallback),
                     "requestSatelliteListeningEnabled");
         }
 
         @Override
-        public void requestSatelliteEnabled(boolean enable, IIntegerConsumer errorCallback)
-                throws RemoteException {
+        public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.requestSatelliteEnabled(enable, errorCallback),
+                    () -> SatelliteImplBase.this
+                            .requestSatelliteEnabled(
+                                    enableSatellite, enableDemoMode, errorCallback),
                     "requestSatelliteEnabled");
         }
 
@@ -131,19 +132,11 @@
         }
 
         @Override
-        public void requestMaxCharactersPerMOTextMessage(IIntegerConsumer errorCallback,
-                IIntegerConsumer callback) throws RemoteException {
+        public void provisionSatelliteService(String token, String regionId,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestMaxCharactersPerMOTextMessage(errorCallback, callback),
-                    "requestMaxCharactersPerMOTextMessage");
-        }
-
-        @Override
-        public void provisionSatelliteService(String token, IIntegerConsumer errorCallback)
-                throws RemoteException {
-            executeMethodAsync(
-                    () -> SatelliteImplBase.this.provisionSatelliteService(token, errorCallback),
+                            .provisionSatelliteService(token, regionId, errorCallback),
                     "provisionSatelliteService");
         }
 
@@ -173,12 +166,11 @@
         }
 
         @Override
-        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isDemoMode,
-                boolean isEmergency, IIntegerConsumer errorCallback) throws RemoteException {
+        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .sendSatelliteDatagram(
-                                    datagram, isDemoMode, isEmergency, errorCallback),
+                            .sendSatelliteDatagram(datagram, isEmergency, errorCallback),
                     "sendSatelliteDatagram");
         }
 
@@ -249,7 +241,6 @@
      * Listening mode allows the satellite service to listen for incoming pages.
      *
      * @param enable True to enable satellite listening mode and false to disable.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
      * @param errorCallback The callback to receive the error code result of the operation.
@@ -264,17 +255,18 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, int timeout,
+    public void requestSatelliteListeningEnabled(boolean enable, int timeout,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled,
-     * this will also disable the cellular modem, and if the satellite modem is disabled,
-     * this will also re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+     * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable True to enable the satellite modem and false to disable.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True to enable demo mode and false to disable.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -287,7 +279,8 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void requestSatelliteEnabled(boolean enable, @NonNull IIntegerConsumer errorCallback) {
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+            @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
@@ -402,35 +395,13 @@
     }
 
     /**
-     * Request to get the maximum number of characters per MO text message on satellite.
-     *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the maximum number of characters per MO text message on satellite.
-     *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     */
-    public void requestMaxCharactersPerMOTextMessage(@NonNull IIntegerConsumer errorCallback,
-            @NonNull IIntegerConsumer callback) {
-        // stub implementation
-    }
-
-    /**
      * Provision the device with a satellite provider.
      * This is needed if the provider allows dynamic registration.
      * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
+     * @param regionId The region ID for the device's current location.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -446,7 +417,7 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    public void provisionSatelliteService(@NonNull String token,
+    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
@@ -530,7 +501,6 @@
      * Send datagram over satellite.
      *
      * @param datagram Datagram to send in byte format.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param isEmergency Whether this is an emergency datagram.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
@@ -550,8 +520,8 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isDemoMode,
-            boolean isEmergency, @NonNull IIntegerConsumer errorCallback) {
+    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+            @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 323fa6f..d0de3ac 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -68,7 +68,7 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
-import android.telephony.satellite.ISatellitePositionUpdateCallback;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
 import android.telephony.satellite.SatelliteCapabilities;
@@ -2726,11 +2726,13 @@
      *
      * @param subId The subId of the subscription to enable or disable the satellite modem for.
      * @param enable True to enable the satellite modem and false to disable.
-     * @param callback The callback to get the error code of the request.
+     * @param isDemoModeEnabled True if demo mode is enabled and false otherwise.
+     * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteEnabled(int subId, boolean enable, in IIntegerConsumer callback);
+    void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled,
+            in IIntegerConsumer callback);
 
     /**
      * Request to get whether the satellite modem is enabled.
@@ -2744,17 +2746,6 @@
     void requestIsSatelliteEnabled(int subId, in ResultReceiver receiver);
 
     /**
-     * Request to enable or disable the satellite service demo mode.
-     *
-     * @param subId The subId of the subscription to enable or disable the satellite demo mode for.
-     * @param enable True to enable the satellite demo mode and false to disable.
-     * @param callback The callback to get the error code of the request.
-     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
-            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteDemoModeEnabled(int subId, boolean enable, in IIntegerConsumer callback);
-
-    /**
      * Request to get whether the satellite service demo mode is enabled.
      *
      * @param subId The subId of the subscription to request whether the satellite demo mode is
@@ -2764,7 +2755,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsSatelliteDemoModeEnabled(int subId, in ResultReceiver receiver);
+    void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
 
     /**
      * Request to get whether the satellite service is supported on the device.
@@ -2787,39 +2778,28 @@
     void requestSatelliteCapabilities(int subId, in ResultReceiver receiver);
 
     /**
-     * Start receiving satellite pointing updates.
+     * Start receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscription to stop satellite position updates for.
-     * @param errorCallback The callback to get the error code of the request.
-     * @param callback The callback to handle position updates.
+     * @param subId The subId of the subscription to stop satellite transmission updates for.
+     * @param resultCallback The callback to get the result of the request.
+     * @param callback The callback to handle transmission updates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void startSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
-            in ISatellitePositionUpdateCallback callback);
+    void startSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+            in ISatelliteTransmissionUpdateCallback callback);
 
     /**
-     * Stop receiving satellite pointing updates.
+     * Stop receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscritpion to stop satellite position updates for.
-     * @param errorCallback The callback to get the error code of the request.
-     * @param callback The callback that was passed to startSatellitePositionUpdates.
+     * @param subId The subId of the subscritpion to stop satellite transmission updates for.
+     * @param resultCallback The callback to get the result of the request.
+     * @param callback The callback that was passed to startSatelliteTransmissionUpdates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void stopSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
-            in ISatellitePositionUpdateCallback callback);
-
-    /**
-     * Request to get the maximum number of bytes per datagram that can be sent to satellite.
-     *
-     * @param subId The subId of the subscription to get the maximum number of characters for.
-     * @param receiver Result receiver to get the error code of the request and the requested
-     *                 maximum number of bytes per datagram that can be sent to satellite.
-     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
-            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestMaxSizePerSendingDatagram(int subId, in ResultReceiver receiver);
+    void stopSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+            in ISatelliteTransmissionUpdateCallback callback);
 
     /**
      * Register the subscription with a satellite provider.
@@ -2828,13 +2808,14 @@
      * @param subId The subId of the subscription to be provisioned.
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param callback The callback to get the error code of the request.
+     * @param regionId The region ID for the device's current location.
+     * @param callback The callback to get the result of the request.
      *
      * @return The signal transport used by callers to cancel the provision request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    ICancellationSignal provisionSatelliteService(int subId, in String token,
+    ICancellationSignal provisionSatelliteService(int subId, in String token, in String regionId,
             in IIntegerConsumer callback);
 
     /**
@@ -2846,7 +2827,7 @@
      *
      * @param subId The subId of the subscription to be deprovisioned.
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param callback The callback to get the error code of the request.
+     * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2916,15 +2897,13 @@
      * Register to receive incoming datagrams over satellite.
      *
      * @param subId The subId of the subscription to register for incoming satellite datagrams.
-     * @param datagramType Type of datagram.
      * @param callback The callback to handle the incoming datagrams.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteDatagram(
-            int subId, int datagramType, ISatelliteDatagramCallback callback);
+    int registerForSatelliteDatagram(int subId, ISatelliteDatagramCallback callback);
 
    /**
      * Unregister to stop receiving incoming datagrams over satellite.
@@ -2941,7 +2920,7 @@
     * Poll pending satellite datagrams over satellite.
     *
     * @param subId The subId of the subscription used for receiving datagrams.
-    * @param callback The callback to get the error code of the request.
+    * @param callback The callback to get the result of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
                 + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2955,7 +2934,7 @@
     * @param datagram Datagram to send over satellite.
     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
     *                                 full screen mode.
-    * @param callback The callback to get the error code of the request.
+    * @param callback The callback to get the result of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
diff --git a/tests/EnforcePermission/Android.bp b/tests/EnforcePermission/Android.bp
new file mode 100644
index 0000000..719a898
--- /dev/null
+++ b/tests/EnforcePermission/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "frameworks-enforce-permission-test-aidl",
+    srcs: ["aidl/**/*.aidl"],
+}
diff --git a/tests/EnforcePermission/TEST_MAPPING b/tests/EnforcePermission/TEST_MAPPING
new file mode 100644
index 0000000..a1bf42a
--- /dev/null
+++ b/tests/EnforcePermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "EnforcePermissionTests"
+    }
+  ]
+}
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
new file mode 100644
index 0000000..1eb773d
--- /dev/null
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.tests.enforcepermission;
+
+interface INested {
+    @EnforcePermission("ACCESS_NETWORK_STATE")
+    void ProtectedByAccessNetworkState();
+
+    @EnforcePermission("READ_SYNC_SETTINGS")
+    void ProtectedByReadSyncSettings();
+}
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
new file mode 100644
index 0000000..18e3aec
--- /dev/null
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tests.enforcepermission;
+
+interface IProtected {
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternet();
+
+    @EnforcePermission("VIBRATE")
+    void ProtectedByVibrate();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndVibrateImplicitly();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndAccessNetworkStateImplicitly();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndReadSyncSettingsImplicitly();
+}
diff --git a/tests/EnforcePermission/service-app/Android.bp b/tests/EnforcePermission/service-app/Android.bp
new file mode 100644
index 0000000..a4ac1d7
--- /dev/null
+++ b/tests/EnforcePermission/service-app/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "EnforcePermissionTestHelper",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/tests/EnforcePermission/service-app/AndroidManifest.xml
similarity index 64%
copy from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
copy to tests/EnforcePermission/service-app/AndroidManifest.xml
index 9bf9254..ddafe15 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/tests/EnforcePermission/service-app/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- 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.
@@ -14,13 +14,14 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.frameworks.servicestests.install_uses_sdk">
-
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
-        <!-- This will fail to install, because minExtensionVersion is not met -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
-    </uses-sdk>
-
+  package="android.tests.enforcepermission.service">
     <application>
+        <service
+          android:name=".TestService"
+          android:exported="true" />
+
+        <service
+          android:name=".NestedTestService"
+          android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
new file mode 100644
index 0000000..7879a12
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
@@ -0,0 +1,48 @@
+/**
+ * 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.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.tests.enforcepermission.INested;
+import android.util.Log;
+
+public class NestedTestService extends Service {
+    private static final String TAG = "EnforcePermission.NestedTestService";
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind");
+        return mBinder;
+    }
+
+    private final INested.Stub mBinder = new INested.Stub() {
+        @Override
+        @EnforcePermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+        public void ProtectedByAccessNetworkState() {
+            ProtectedByAccessNetworkState_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.READ_SYNC_SETTINGS)
+        public void ProtectedByReadSyncSettings() {
+            ProtectedByReadSyncSettings_enforcePermission();
+        }
+    };
+}
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
new file mode 100644
index 0000000..e9b897d
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
@@ -0,0 +1,119 @@
+/**
+ * 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.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.INested;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class TestService extends Service {
+
+    private static final String TAG = "EnforcePermission.TestService";
+    private volatile ServiceConnection mNestedServiceConnection;
+
+    @Override
+    public void onCreate() {
+        mNestedServiceConnection = new ServiceConnection();
+        Intent intent = new Intent(this, NestedTestService.class);
+        boolean bound = bindService(intent, mNestedServiceConnection, Context.BIND_AUTO_CREATE);
+        if (!bound) {
+            Log.wtf(TAG, "bindService() on NestedTestService failed");
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        unbindService(mNestedServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<INested> mFuture = new CompletableFuture<>();
+
+        public INested get() {
+            try {
+                return mFuture.get(1, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach NestedTestService: " + e.getMessage());
+            }
+        }
+
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(INested.Stub.asInterface(service));
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+    };
+
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private final IProtected.Stub mBinder = new IProtected.Stub() {
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternet() {
+            ProtectedByInternet_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.VIBRATE)
+        public void ProtectedByVibrate() {
+            ProtectedByVibrate_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndVibrateImplicitly() {
+            ProtectedByInternetAndVibrateImplicitly_enforcePermission();
+
+            ProtectedByVibrate();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndAccessNetworkStateImplicitly() throws RemoteException {
+            ProtectedByInternetAndAccessNetworkStateImplicitly_enforcePermission();
+
+            mNestedServiceConnection.get().ProtectedByAccessNetworkState();
+
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndReadSyncSettingsImplicitly() throws RemoteException {
+            ProtectedByInternetAndReadSyncSettingsImplicitly_enforcePermission();
+
+            mNestedServiceConnection.get().ProtectedByReadSyncSettings();
+        }
+    };
+}
diff --git a/tests/EnforcePermission/test-app/Android.bp b/tests/EnforcePermission/test-app/Android.bp
new file mode 100644
index 0000000..cd53854
--- /dev/null
+++ b/tests/EnforcePermission/test-app/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "EnforcePermissionTests",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    data: [
+        ":EnforcePermissionTestHelper",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/tests/EnforcePermission/test-app/AndroidManifest.xml b/tests/EnforcePermission/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..4a0c6a8
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="android.tests.enforcepermission.tests">
+
+    <!-- Expected for the tests (not actually used) -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+
+    <queries>
+        <package android:name="android.tests.enforcepermission.service" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.tests.enforcepermission.tests"/>
+</manifest>
diff --git a/tests/EnforcePermission/test-app/AndroidTest.xml b/tests/EnforcePermission/test-app/AndroidTest.xml
new file mode 100644
index 0000000..120381a
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs EnforcePermission End-to-End Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+      <option name="test-file-name" value="EnforcePermissionTestHelper.apk"/>
+      <option name="test-file-name" value="EnforcePermissionTests.apk"/>
+      <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <option name="test-tag" value="EnforcePermissionTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.tests.enforcepermission.tests"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
new file mode 100644
index 0000000..d2a4a03
--- /dev/null
+++ b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
@@ -0,0 +1,129 @@
+/**
+ * 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.tests.enforcepermission.tests;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class ServiceTest {
+
+    private static final String TAG = "EnforcePermission.Tests";
+    private static final String SERVICE_NAME = "android.tests.enforcepermission.service";
+    private static final int SERVICE_TIMEOUT_SEC = 5;
+
+    private Context mContext;
+    private volatile ServiceConnection mServiceConnection;
+
+    @Before
+    public void bindTestService() throws Exception {
+        Log.d(TAG, "bindTestService");
+        mContext = InstrumentationRegistry.getTargetContext();
+        mServiceConnection = new ServiceConnection();
+        Intent intent = new Intent();
+        intent.setClassName(SERVICE_NAME, SERVICE_NAME + ".TestService");
+        assertTrue(mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE));
+    }
+
+    @After
+    public void unbindTestService() throws Exception {
+        mContext.unbindService(mServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<IProtected> mFuture = new CompletableFuture<>();
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(IProtected.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+
+        public IProtected get() {
+            try {
+                return mFuture.get(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach TestService: " + e.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testImmediatePermissionGranted_succeeds()
+            throws RemoteException {
+        mServiceConnection.get().ProtectedByInternet();
+    }
+
+    @Test
+    public void testImmediatePermissionNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByVibrate());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedButImplicitLocalNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByInternetAndVibrateImplicitly());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedButImplicitNestedNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get()
+                      .ProtectedByInternetAndAccessNetworkStateImplicitly());
+        assertThat(ex.getMessage(), containsString("ACCESS_NETWORK_STATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedAndImplicitNestedGranted_succeeds()
+            throws RemoteException {
+        mServiceConnection.get().ProtectedByInternetAndReadSyncSettingsImplicitly();
+    }
+}
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index e704cc2..46ebfed 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -35,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IllformedLocaleException;
 import java.util.List;
 import java.util.Locale;
@@ -141,14 +142,118 @@
 
         HashMap<String, LocaleInfo> result =
                 LocaleStore.convertExplicitLocales(locales, supportedLocale);
-
         assertEquals("en", result.get("en").getId());
         assertEquals("en-US", result.get("en-US").getId());
         assertNull(result.get("en-Latn-US"));
     }
 
+    @Test
+    public void getLevelLocales_languageTier_returnAllSupportLanguages() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,ja-JP,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,bn-IN");
+
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = null;
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(5, localeInfos.size());
+        localeInfos.forEach(localeInfo -> {
+            assertTrue(localeInfo.getLocale().getCountry().isEmpty());
+        });
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("zh-Hant")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("ja")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("ks-Arab")));
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsEn_returnEnLocales() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("en"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(3, localeInfos.size());
+        localeInfos.forEach(localeInfo -> {
+            assertEquals("en", localeInfo.getLocale().getLanguage());
+        });
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-US")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-GB")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-ZA")));
+    }
+
+    @Test
+    public void getLevelLocales_numberingTierAndParentIsBnIn_returnBnInLocales() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(1, localeInfos.size());
+        assertEquals("bn-IN", localeInfos.iterator().next().getLocale().toLanguageTag());
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsBnInAndIgnoreBn_returnEmpty() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("bn-IN");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(0, localeInfos.size());
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsBnIn_returnBnLocaleFamily() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("en-US");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(3, localeInfos.size());
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-adlm")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-arab")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN")));
+    }
+
     private ArrayList<LocaleInfo> getFakeSupportedLocales() {
-        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
+        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB", "en-US-u-nu-arab"};
         ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
         for (String localeTag : locales) {
             supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));