Merge "docs: Typo fix in TextLinks.Request.Builder" into udc-dev
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 e2fff0f..deb8a63 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -766,11 +766,11 @@
          * Default quota for pre-S apps. The same as allowing an alarm slot once
          * every ALLOW_WHILE_IDLE_LONG_DELAY, which was 9 minutes.
          */
-        private static final int DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA = 1;
+        private static final int DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA = 7;
         private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72;
 
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
-        private static final long DEFAULT_ALLOW_WHILE_IDLE_COMPAT_WINDOW = 9 * 60 * 1000; // 9 mins.
+        private static final long DEFAULT_ALLOW_WHILE_IDLE_COMPAT_WINDOW = 60 * 60 * 1000;
 
         private static final long DEFAULT_PRIORITY_ALARM_DELAY = 9 * 60_000;
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e6d6361..c14de70 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13040,6 +13040,21 @@
     method public int getStart();
   }
 
+  public final class DetectedPhrase implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getId();
+    method @Nullable public String getPhrase();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.DetectedPhrase> CREATOR;
+  }
+
+  public static final class DetectedPhrase.Builder {
+    ctor public DetectedPhrase.Builder();
+    method @NonNull public android.service.voice.DetectedPhrase build();
+    method @NonNull public android.service.voice.DetectedPhrase.Builder setId(int);
+    method @NonNull public android.service.voice.DetectedPhrase.Builder setPhrase(@NonNull String);
+  }
+
   public abstract class DetectorFailure implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public String getErrorMessage();
@@ -13080,12 +13095,13 @@
     method public int getAudioChannel();
     method @NonNull public java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams();
     method public int getConfidenceLevel();
+    method @NonNull public android.service.voice.DetectedPhrase getDetectedPhrase();
     method @NonNull public android.os.PersistableBundle getExtras();
     method public int getHotwordDurationMillis();
     method public int getHotwordOffsetMillis();
-    method public int getHotwordPhraseId();
+    method @Deprecated public int getHotwordPhraseId();
     method public static int getMaxBundleSize();
-    method public static int getMaxHotwordPhraseId();
+    method @Deprecated public static int getMaxHotwordPhraseId();
     method public static int getMaxScore();
     method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
     method public int getPersonalizedScore();
@@ -13114,11 +13130,12 @@
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioChannel(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioStreams(@NonNull java.util.List<android.service.voice.HotwordAudioStream>);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setConfidenceLevel(int);
+    method @NonNull public android.service.voice.HotwordDetectedResult.Builder setDetectedPhrase(@NonNull android.service.voice.DetectedPhrase);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setHotwordDetectionPersonalized(boolean);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setHotwordDurationMillis(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setHotwordOffsetMillis(int);
-    method @NonNull public android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int);
+    method @Deprecated @NonNull public android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setMediaSyncEvent(@NonNull android.media.MediaSyncEvent);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setPersonalizedScore(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setScore(int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3baf5a2..def3fa9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1444,6 +1444,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
     field public static final String DISPLAY_CATEGORY_REAR = "android.hardware.display.category.REAR";
+    field public static final String HDR_OUTPUT_CONTROL_FLAG = "enable_hdr_output_control";
     field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
     field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
     field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3
@@ -3310,6 +3311,7 @@
 
   public interface WindowManager extends android.view.ViewManager {
     method public default int getDisplayImePolicy(int);
+    method public static boolean hasWindowExtensionsEnabled();
     method public default void holdLock(android.os.IBinder, int);
     method public default boolean isGlobalKey(int);
     method public default boolean isTaskSnapshotSupported();
@@ -3732,7 +3734,7 @@
 
   public final class TaskFragmentCreationParams implements android.os.Parcelable {
     method @NonNull public android.os.IBinder getFragmentToken();
-    method @NonNull public android.graphics.Rect getInitialBounds();
+    method @NonNull public android.graphics.Rect getInitialRelativeBounds();
     method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
     method @NonNull public android.os.IBinder getOwnerToken();
     method public int getWindowingMode();
@@ -3742,7 +3744,7 @@
   public static final class TaskFragmentCreationParams.Builder {
     ctor public TaskFragmentCreationParams.Builder(@NonNull android.window.TaskFragmentOrganizerToken, @NonNull android.os.IBinder, @NonNull android.os.IBinder);
     method @NonNull public android.window.TaskFragmentCreationParams build();
-    method @NonNull public android.window.TaskFragmentCreationParams.Builder setInitialBounds(@NonNull android.graphics.Rect);
+    method @NonNull public android.window.TaskFragmentCreationParams.Builder setInitialRelativeBounds(@NonNull android.graphics.Rect);
     method @NonNull public android.window.TaskFragmentCreationParams.Builder setWindowingMode(int);
   }
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 87c77c2..ef5cd93 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5249,17 +5249,17 @@
             boolean hasSecondLine = showProgress;
             if (p.hasTitle()) {
                 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
-                contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title));
+                contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.mTitle));
                 setTextViewColorPrimary(contentView, p.mTitleViewId, p);
             } else if (p.mTitleViewId != R.id.title) {
                 // This alternate title view ID is not cleared by resetStandardTemplate
                 contentView.setViewVisibility(p.mTitleViewId, View.GONE);
                 contentView.setTextViewText(p.mTitleViewId, null);
             }
-            if (p.text != null && p.text.length() != 0
+            if (p.mText != null && p.mText.length() != 0
                     && (!showProgress || p.mAllowTextWithProgress)) {
                 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE);
-                contentView.setTextViewText(p.mTextViewId, processTextSpans(p.text));
+                contentView.setTextViewText(p.mTextViewId, processTextSpans(p.mText));
                 setTextViewColorSecondary(contentView, p.mTextViewId, p);
                 hasSecondLine = true;
             } else if (p.mTextViewId != R.id.text) {
@@ -5533,20 +5533,20 @@
             if (p.mHideSubText) {
                 return false;
             }
-            CharSequence summaryText = p.summaryText;
-            if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
+            CharSequence headerText = p.mSubText;
+            if (headerText == null && mStyle != null && mStyle.mSummaryTextSet
                     && mStyle.hasSummaryInHeader()) {
-                summaryText = mStyle.mSummaryText;
+                headerText = mStyle.mSummaryText;
             }
-            if (summaryText == null
+            if (headerText == null
                     && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
                     && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
-                summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
+                headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT);
             }
-            if (!TextUtils.isEmpty(summaryText)) {
+            if (!TextUtils.isEmpty(headerText)) {
                 // TODO: Remove the span entirely to only have the string with propper formating.
                 contentView.setTextViewText(R.id.header_text, processTextSpans(
-                        processLegacyText(summaryText)));
+                        processLegacyText(headerText)));
                 setTextViewColorSecondary(contentView, R.id.header_text, p);
                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
                 if (hasTextToLeft) {
@@ -5566,9 +5566,9 @@
             if (p.mHideSubText) {
                 return false;
             }
-            if (!TextUtils.isEmpty(p.headerTextSecondary)) {
+            if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) {
                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
-                        processLegacyText(p.headerTextSecondary)));
+                        processLegacyText(p.mHeaderTextSecondary)));
                 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p);
                 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
                 if (hasTextToLeft) {
@@ -6175,7 +6175,7 @@
                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
                     .highlightExpander(false)
                     .fillTextsFrom(this);
-            if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) {
+            if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) {
                 p.summaryText(createSummaryText());
             }
             RemoteViews header = makeNotificationHeader(p);
@@ -7192,7 +7192,7 @@
             checkBuilder();
 
             if (mBigContentTitle != null) {
-                p.title = mBigContentTitle;
+                p.mTitle = mBigContentTitle;
             }
 
             return mBuilder.applyStandardTemplateWithActions(layoutId, p, result);
@@ -12455,10 +12455,10 @@
         boolean mAllowTextWithProgress;
         int mTitleViewId;
         int mTextViewId;
-        CharSequence title;
-        CharSequence text;
-        CharSequence headerTextSecondary;
-        CharSequence summaryText;
+        @Nullable CharSequence mTitle;
+        @Nullable CharSequence mText;
+        @Nullable CharSequence mHeaderTextSecondary;
+        @Nullable CharSequence mSubText;
         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
         boolean allowColorization  = true;
         boolean mHighlightExpander = false;
@@ -12480,10 +12480,10 @@
             mAllowTextWithProgress = false;
             mTitleViewId = R.id.title;
             mTextViewId = R.id.text;
-            title = null;
-            text = null;
-            summaryText = null;
-            headerTextSecondary = null;
+            mTitle = null;
+            mText = null;
+            mSubText = null;
+            mHeaderTextSecondary = null;
             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
             allowColorization = true;
             mHighlightExpander = false;
@@ -12491,7 +12491,7 @@
         }
 
         final boolean hasTitle() {
-            return !TextUtils.isEmpty(title) && !mHideTitle;
+            return !TextUtils.isEmpty(mTitle) && !mHideTitle;
         }
 
         final StandardTemplateParams viewType(int viewType) {
@@ -12564,23 +12564,23 @@
             return this;
         }
 
-        final StandardTemplateParams title(CharSequence title) {
-            this.title = title;
+        final StandardTemplateParams title(@Nullable CharSequence title) {
+            this.mTitle = title;
             return this;
         }
 
-        final StandardTemplateParams text(CharSequence text) {
-            this.text = text;
+        final StandardTemplateParams text(@Nullable CharSequence text) {
+            this.mText = text;
             return this;
         }
 
-        final StandardTemplateParams summaryText(CharSequence text) {
-            this.summaryText = text;
+        final StandardTemplateParams summaryText(@Nullable CharSequence text) {
+            this.mSubText = text;
             return this;
         }
 
-        final StandardTemplateParams headerTextSecondary(CharSequence text) {
-            this.headerTextSecondary = text;
+        final StandardTemplateParams headerTextSecondary(@Nullable CharSequence text) {
+            this.mHeaderTextSecondary = text;
             return this;
         }
 
@@ -12607,9 +12607,9 @@
 
         final StandardTemplateParams fillTextsFrom(Builder b) {
             Bundle extras = b.mN.extras;
-            this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
-            this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
-            this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
+            this.mTitle = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
+            this.mText = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
+            this.mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
             return this;
         }
 
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 5df2d5e..de4f619 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1194,6 +1194,20 @@
         }
     }
 
+    /**
+     * Enable or disable secure transport for testing. Defaults to enabled.
+     *
+     * @param enabled true to enable. false to disable.
+     * @hide
+     */
+    public void enableSecureTransport(boolean enabled) {
+        try {
+            mService.enableSecureTransport(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private boolean checkFeaturePresent() {
         boolean featurePresent = mService != null;
         if (!featurePresent && DEBUG) {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 010aa8f..cb4baca 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -88,4 +88,6 @@
     void enableSystemDataSync(int associationId, int flags);
 
     void disableSystemDataSync(int associationId, int flags);
+
+    void enableSecureTransport(boolean enabled);
 }
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 7707289..856bde8 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -70,6 +70,12 @@
     public final int logHistorySize;
 
     /**
+     * Disable flush when receiving a VIEW_TREE_APPEARING event.
+     * @hide
+     */
+    public final boolean disableFlushForViewTreeAppearing;
+
+    /**
      * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
      * for all acitivites in the package).
      */
@@ -90,7 +96,8 @@
     public ContentCaptureOptions(int loggingLevel) {
         this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
                 /* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
-                /* logHistorySize= */ 0, /* whitelistedComponents= */ null);
+                /* logHistorySize= */ 0, /* disableFlushForViewTreeAppearing= */ false,
+                /* whitelistedComponents= */ null);
     }
 
     /**
@@ -98,10 +105,23 @@
      */
     public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
             int textChangeFlushingFrequencyMs, int logHistorySize,
-            @SuppressLint("NullableCollection")
+            @SuppressLint({"ConcreteCollection", "NullableCollection"})
             @Nullable ArraySet<ComponentName> whitelistedComponents) {
         this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
-                textChangeFlushingFrequencyMs, logHistorySize, whitelistedComponents);
+                textChangeFlushingFrequencyMs, logHistorySize,
+                ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                whitelistedComponents);
+    }
+
+    /** @hide */
+    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs, int logHistorySize,
+            boolean disableFlushForViewTreeAppearing,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"})
+            @Nullable ArraySet<ComponentName> whitelistedComponents) {
+        this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
+                textChangeFlushingFrequencyMs, logHistorySize, disableFlushForViewTreeAppearing,
+                whitelistedComponents);
     }
 
     /** @hide */
@@ -111,11 +131,14 @@
                 ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
                 ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
                 ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
-                ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, whitelistedComponents);
+                ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
+                ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                whitelistedComponents);
     }
 
     private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
             int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
+            boolean disableFlushForViewTreeAppearing,
             @Nullable ArraySet<ComponentName> whitelistedComponents) {
         this.lite = lite;
         this.loggingLevel = loggingLevel;
@@ -123,6 +146,7 @@
         this.idleFlushingFrequencyMs = idleFlushingFrequencyMs;
         this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
         this.logHistorySize = logHistorySize;
+        this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
         this.whitelistedComponents = whitelistedComponents;
     }
 
@@ -171,7 +195,8 @@
             .append(", maxBufferSize=").append(maxBufferSize)
             .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
             .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
-            .append(", logHistorySize=").append(logHistorySize);
+            .append(", logHistorySize=").append(logHistorySize)
+            .append(", disableFlushForViewTreeAppearing=").append(disableFlushForViewTreeAppearing);
         if (whitelistedComponents != null) {
             string.append(", whitelisted=").append(whitelistedComponents);
         }
@@ -189,6 +214,7 @@
         pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
         pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
         pw.print(", logSize="); pw.print(logHistorySize);
+        pw.print(", disableFlushForViewTreeAppearing="); pw.print(disableFlushForViewTreeAppearing);
         if (whitelistedComponents != null) {
             pw.print(", whitelisted="); pw.print(whitelistedComponents);
         }
@@ -209,6 +235,7 @@
         parcel.writeInt(idleFlushingFrequencyMs);
         parcel.writeInt(textChangeFlushingFrequencyMs);
         parcel.writeInt(logHistorySize);
+        parcel.writeBoolean(disableFlushForViewTreeAppearing);
         parcel.writeArraySet(whitelistedComponents);
     }
 
@@ -226,12 +253,13 @@
                     final int idleFlushingFrequencyMs = parcel.readInt();
                     final int textChangeFlushingFrequencyMs = parcel.readInt();
                     final int logHistorySize = parcel.readInt();
+                    final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
                     @SuppressWarnings("unchecked")
                     final ArraySet<ComponentName> whitelistedComponents =
                             (ArraySet<ComponentName>) parcel.readArraySet(null);
                     return new ContentCaptureOptions(loggingLevel, maxBufferSize,
                             idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
-                            whitelistedComponents);
+                            disableFlushForViewTreeAppearing, whitelistedComponents);
                 }
 
                 @Override
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c8db0d8..795c77f 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -342,15 +342,16 @@
             final int enumCheckUriPermission =
                     GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_CHECK_URI_PERMISSION;
             if (permissionCheckPassed) {
-                // Just for logging for mediaProvider cases
-                final ProviderInfo cpi = mContext.getPackageManager()
-                        .resolveContentProvider(uri.getAuthority(),
-                                PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
-                final int callingUserId = UserHandle.getUserId(callingUid);
-                final Uri userUri = (mSingleUser
-                        && !UserHandle.isSameUser(mMyUid, callingUid))
-                        ? maybeAddUserId(uri, callingUserId) : uri;
                 try {
+                    // Just for logging for mediaProvider cases
+                    final ProviderInfo cpi = mContext.getPackageManager()
+                            .resolveContentProvider(uri.getAuthority(),
+                                    PackageManager.ComponentInfoFlags.of(
+                                            PackageManager.GET_META_DATA));
+                    final int callingUserId = UserHandle.getUserId(callingUid);
+                    final Uri userUri = (mSingleUser
+                            && !UserHandle.isSameUser(mMyUid, callingUid))
+                            ? maybeAddUserId(uri, callingUserId) : uri;
                     if (cpi.forceUriPermissions
                             && mInterface.checkUriPermission(uri,
                             callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
@@ -362,7 +363,7 @@
                                 enumCheckUriPermission,
                                 callingUid, uri.getAuthority(), type);
                     }
-                } catch (RemoteException e) {
+                } catch (Exception e) {
                     //does nothing
                 }
             } else {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index b84eb11..456d218 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -930,7 +930,8 @@
         if (provider != null) {
             try {
                 final StringResultListener resultListener = new StringResultListener();
-                provider.getTypeAsync(url, new RemoteCallback(resultListener));
+                provider.getTypeAsync(mContext.getAttributionSource(),
+                        url, new RemoteCallback(resultListener));
                 resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
                 if (resultListener.exception != null) {
                     throw resultListener.exception;
@@ -944,7 +945,11 @@
                 Log.w(TAG, "Failed to get type for: " + url + " (" + e.getMessage() + ")");
                 return null;
             } finally {
-                releaseProvider(provider);
+                try {
+                    releaseProvider(provider);
+                } catch (java.lang.NullPointerException e) {
+                    // does nothing, Binder connection already null
+                }
             }
         }
 
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index ca537a4..1775bf7 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1209,6 +1209,14 @@
      * @hide
      */
     @Override
+    public int getAssociatedDisplayId() {
+        return mBase.getAssociatedDisplayId();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
     public void updateDisplay(int displayId) {
         mBase.updateDisplay(displayId);
     }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 7766896..8acdf51 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3981,8 +3981,7 @@
     /**
      * Details for requesting the pre-commit install approval.
      */
-    @DataClass(genParcelable = true, genHiddenConstructor = true, genBuilder = true,
-            genToString = true)
+    @DataClass(genConstructor = false, genToString = true)
     public static final class PreapprovalDetails implements Parcelable {
         /**
          * The icon representing the app to be installed.
@@ -4001,6 +4000,161 @@
          */
         private final @NonNull String mPackageName;
 
+        /**
+         * Creates a new PreapprovalDetails.
+         *
+         * @param icon
+         *   The icon representing the app to be installed.
+         * @param label
+         *   The label representing the app to be installed.
+         * @param locale
+         *   The locale of the app label being used.
+         * @param packageName
+         *   The package name of the app to be installed.
+         * @hide
+         */
+        public PreapprovalDetails(
+                @Nullable Bitmap icon,
+                @NonNull CharSequence label,
+                @NonNull ULocale locale,
+                @NonNull String packageName) {
+            mIcon = icon;
+            mLabel = label;
+            Preconditions.checkArgument(!TextUtils.isEmpty(mLabel),
+                    "App label cannot be empty.");
+            mLocale = locale;
+            Preconditions.checkArgument(!Objects.isNull(mLocale),
+                    "Locale cannot be null.");
+            mPackageName = packageName;
+            Preconditions.checkArgument(!TextUtils.isEmpty(mPackageName),
+                    "Package name cannot be empty.");
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            byte flg = 0;
+            if (mIcon != null) flg |= 0x1;
+            dest.writeByte(flg);
+            if (mIcon != null) mIcon.writeToParcel(dest, flags);
+            dest.writeCharSequence(mLabel);
+            dest.writeString8(mLocale.toString());
+            dest.writeString8(mPackageName);
+        }
+
+        @Override
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        /* package-private */ PreapprovalDetails(@NonNull Parcel in) {
+            byte flg = in.readByte();
+            final Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in);
+            final CharSequence label = in.readCharSequence();
+            final ULocale locale = new ULocale(in.readString8());
+            final String packageName = in.readString8();
+
+            mIcon = icon;
+            mLabel = label;
+            Preconditions.checkArgument(!TextUtils.isEmpty(mLabel),
+                    "App label cannot be empty.");
+            mLocale = locale;
+            Preconditions.checkArgument(!Objects.isNull(mLocale),
+                    "Locale cannot be null.");
+            mPackageName = packageName;
+            Preconditions.checkArgument(!TextUtils.isEmpty(mPackageName),
+                    "Package name cannot be empty.");
+        }
+
+        public static final @NonNull Parcelable.Creator<PreapprovalDetails> CREATOR
+                = new Parcelable.Creator<PreapprovalDetails>() {
+            @Override
+            public PreapprovalDetails[] newArray(int size) {
+                return new PreapprovalDetails[size];
+            }
+
+            @Override
+            public PreapprovalDetails createFromParcel(@NonNull Parcel in) {
+                return new PreapprovalDetails(in);
+            }
+        };
+
+        /**
+         * A builder for {@link PreapprovalDetails}
+         */
+        public static final class Builder {
+
+            private @Nullable Bitmap mIcon;
+            private @NonNull CharSequence mLabel;
+            private @NonNull ULocale mLocale;
+            private @NonNull String mPackageName;
+
+            private long mBuilderFieldsSet = 0L;
+
+            /**
+             * Creates a new Builder.
+             */
+            public Builder() {}
+
+            /**
+             * The icon representing the app to be installed.
+             */
+            public @NonNull Builder setIcon(@NonNull Bitmap value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x1;
+                mIcon = value;
+                return this;
+            }
+
+            /**
+             * The label representing the app to be installed.
+             */
+            public @NonNull Builder setLabel(@NonNull CharSequence value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x2;
+                mLabel = value;
+                return this;
+            }
+
+            /**
+             * The locale of the app label being used.
+             */
+            public @NonNull Builder setLocale(@NonNull ULocale value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x4;
+                mLocale = value;
+                return this;
+            }
+
+            /**
+             * The package name of the app to be installed.
+             */
+            public @NonNull Builder setPackageName(@NonNull String value) {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x8;
+                mPackageName = value;
+                return this;
+            }
+
+            /** Builds the instance. This builder should not be touched after calling this! */
+            public @NonNull PreapprovalDetails build() {
+                checkNotUsed();
+                mBuilderFieldsSet |= 0x10; // Mark builder used
+
+                PreapprovalDetails o = new PreapprovalDetails(
+                        mIcon,
+                        mLabel,
+                        mLocale,
+                        mPackageName);
+                return o;
+            }
+
+            private void checkNotUsed() {
+                if ((mBuilderFieldsSet & 0x10) != 0) {
+                    throw new IllegalStateException("This Builder should not be reused. "
+                            + "Use a new Builder instance instead");
+                }
+            }
+        }
+
 
 
 
@@ -4018,39 +4172,6 @@
 
 
         /**
-         * Creates a new PreapprovalDetails.
-         *
-         * @param icon
-         *   The icon representing the app to be installed.
-         * @param label
-         *   The label representing the app to be installed.
-         * @param locale
-         *   The locale of the app label being used.
-         * @param packageName
-         *   The package name of the app to be installed.
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public PreapprovalDetails(
-                @Nullable Bitmap icon,
-                @NonNull CharSequence label,
-                @NonNull ULocale locale,
-                @NonNull String packageName) {
-            this.mIcon = icon;
-            this.mLabel = label;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mLabel);
-            this.mLocale = locale;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mLocale);
-            this.mPackageName = packageName;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mPackageName);
-
-            // onConstructed(); // You can define this method to get a callback
-        }
-
-        /**
          * The icon representing the app to be installed.
          */
         @DataClass.Generated.Member
@@ -4096,155 +4217,11 @@
             " }";
         }
 
-        @Override
-        @DataClass.Generated.Member
-        public void writeToParcel(@NonNull Parcel dest, int flags) {
-            // You can override field parcelling by defining methods like:
-            // void parcelFieldName(Parcel dest, int flags) { ... }
-
-            byte flg = 0;
-            if (mIcon != null) flg |= 0x1;
-            dest.writeByte(flg);
-            if (mIcon != null) mIcon.writeToParcel(dest, flags);
-            dest.writeCharSequence(mLabel);
-            dest.writeString8(mLocale.toString());
-            dest.writeString8(mPackageName);
-        }
-
-        @Override
-        @DataClass.Generated.Member
-        public int describeContents() { return 0; }
-
-        /** @hide */
-        @SuppressWarnings({"unchecked", "RedundantCast"})
-        @DataClass.Generated.Member
-        /* package-private */ PreapprovalDetails(@NonNull Parcel in) {
-            // You can override field unparcelling by defining methods like:
-            // static FieldType unparcelFieldName(Parcel in) { ... }
-
-            byte flg = in.readByte();
-            Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in);
-            CharSequence label = (CharSequence) in.readCharSequence();
-            ULocale locale = new ULocale(in.readString8());
-            String packageName = in.readString8();
-
-            this.mIcon = icon;
-            this.mLabel = label;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mLabel);
-            this.mLocale = locale;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mLocale);
-            this.mPackageName = packageName;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mPackageName);
-
-            // onConstructed(); // You can define this method to get a callback
-        }
-
-        @DataClass.Generated.Member
-        public static final @NonNull Parcelable.Creator<PreapprovalDetails> CREATOR
-                = new Parcelable.Creator<PreapprovalDetails>() {
-            @Override
-            public PreapprovalDetails[] newArray(int size) {
-                return new PreapprovalDetails[size];
-            }
-
-            @Override
-            public PreapprovalDetails createFromParcel(@NonNull Parcel in) {
-                return new PreapprovalDetails(in);
-            }
-        };
-
-        /**
-         * A builder for {@link PreapprovalDetails}
-         */
-        @SuppressWarnings("WeakerAccess")
-        @DataClass.Generated.Member
-        public static final class Builder {
-
-            private @Nullable Bitmap mIcon;
-            private @NonNull CharSequence mLabel;
-            private @NonNull ULocale mLocale;
-            private @NonNull String mPackageName;
-
-            private long mBuilderFieldsSet = 0L;
-
-            /**
-             * Creates a new Builder.
-             */
-            public Builder() {}
-
-            /**
-             * The icon representing the app to be installed.
-             */
-            @DataClass.Generated.Member
-            public @NonNull Builder setIcon(@NonNull Bitmap value) {
-                checkNotUsed();
-                mBuilderFieldsSet |= 0x1;
-                mIcon = value;
-                return this;
-            }
-
-            /**
-             * The label representing the app to be installed.
-             */
-            @DataClass.Generated.Member
-            public @NonNull Builder setLabel(@NonNull CharSequence value) {
-                checkNotUsed();
-                mBuilderFieldsSet |= 0x2;
-                mLabel = value;
-                return this;
-            }
-
-            /**
-             * The locale of the app label being used.
-             */
-            @DataClass.Generated.Member
-            public @NonNull Builder setLocale(@NonNull ULocale value) {
-                checkNotUsed();
-                mBuilderFieldsSet |= 0x4;
-                mLocale = value;
-                return this;
-            }
-
-            /**
-             * The package name of the app to be installed.
-             */
-            @DataClass.Generated.Member
-            public @NonNull Builder setPackageName(@NonNull String value) {
-                checkNotUsed();
-                mBuilderFieldsSet |= 0x8;
-                mPackageName = value;
-                return this;
-            }
-
-            /** Builds the instance. This builder should not be touched after calling this! */
-            public @NonNull PreapprovalDetails build() {
-                checkNotUsed();
-                mBuilderFieldsSet |= 0x10; // Mark builder used
-
-                PreapprovalDetails o = new PreapprovalDetails(
-                        mIcon,
-                        mLabel,
-                        mLocale,
-                        mPackageName);
-                return o;
-            }
-
-            private void checkNotUsed() {
-                if ((mBuilderFieldsSet & 0x10) != 0) {
-                    throw new IllegalStateException(
-                            "This Builder should not be reused. Use a new Builder instance instead");
-                }
-            }
-        }
-
         @DataClass.Generated(
-                time = 1666748098353L,
+                time = 1676970504308L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
-                inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)")
+                inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.PackageInstaller.PreapprovalDetails> CREATOR\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\npublic @java.lang.Override int describeContents()\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate @android.annotation.NonNull java.lang.String mPackageName\nprivate  long mBuilderFieldsSet\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(android.graphics.Bitmap)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(java.lang.CharSequence)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(android.icu.util.ULocale)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(java.lang.String)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails build()\nprivate  void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true)")
         @Deprecated
         private void __metadata() {}
 
@@ -4347,7 +4324,7 @@
         };
 
         @DataClass.Generated(
-                time = 1675135664641L,
+                time = 1676970504336L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
                 inputSignatures = "private  boolean mAllConstraintsSatisfied\npublic  boolean areAllConstraintsSatisfied()\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
@@ -4635,7 +4612,7 @@
         };
 
         @DataClass.Generated(
-                time = 1675135664653L,
+                time = 1676970504352L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
                 inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mDeviceIdleRequired\nprivate final  boolean mAppNotForegroundRequired\nprivate final  boolean mAppNotInteractingRequired\nprivate final  boolean mAppNotTopVisibleRequired\nprivate final  boolean mNotInCallRequired\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mDeviceIdleRequired\nprivate  boolean mAppNotForegroundRequired\nprivate  boolean mAppNotInteractingRequired\nprivate  boolean mAppNotTopVisibleRequired\nprivate  boolean mNotInCallRequired\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index eeff6cc..b766cd1 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -68,6 +68,15 @@
     private static final boolean DEBUG = false;
     private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = true;
 
+    /**
+     * The hdr output control feature flag, the value should be read via
+     * {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} with
+     * {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+     * @hide
+     */
+    @TestApi
+    public static final String HDR_OUTPUT_CONTROL_FLAG = "enable_hdr_output_control";
+
     private final Context mContext;
     private final DisplayManagerGlobal mGlobal;
 
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 6475144..a20191c 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -1108,6 +1108,11 @@
         /**
          * Sets the enabled state of the automatic NAT-T keepalive timers
          *
+         * Note that if this builder was constructed with a {@link IkeTunnelConnectionParams},
+         * but this is called with {@code true}, the framework will automatically choose the
+         * appropriate keepalive timer and ignore the settings in the session params embedded
+         * in the connection params.
+         *
          * @param isEnabled {@code true} to enable automatic keepalive timers, based on internal
          *     platform signals. Defaults to {@code false}.
          * @return this {@link Builder} object to facilitate chaining of method calls
diff --git a/core/java/android/service/voice/DetectedPhrase.aidl b/core/java/android/service/voice/DetectedPhrase.aidl
new file mode 100644
index 0000000..23a405d
--- /dev/null
+++ b/core/java/android/service/voice/DetectedPhrase.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.voice;
+
+parcelable DetectedPhrase;
diff --git a/core/java/android/service/voice/DetectedPhrase.java b/core/java/android/service/voice/DetectedPhrase.java
new file mode 100644
index 0000000..bd90612
--- /dev/null
+++ b/core/java/android/service/voice/DetectedPhrase.java
@@ -0,0 +1,286 @@
+/*
+ * 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.service.voice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Details about the phrase used to generate a {@link HotwordDetectedResult}
+ *
+ * @hide
+ */
+@DataClass(
+        genConstructor = false,
+        genBuilder = true,
+        genEqualsHashCode = true,
+        genHiddenConstDefs = true,
+        genParcelable = true,
+        genToString = true
+)
+@SystemApi
+public final class DetectedPhrase implements Parcelable {
+
+    /**
+     * An ID representing the keyphrase that triggered the successful detection.
+     */
+    private int mId = 0;
+
+    static int defaultHotwordPhraseId() {
+        return 0;
+    }
+
+    /**
+     * A string representing exactly what was heard and interpreted by the service leading to
+     * a successful detection.
+     *
+     * <p>Can be null if not set in {@link DetectedPhrase.Builder}
+     */
+    @Nullable
+    private String mPhrase = null;
+
+    /**
+     * Provides an instance of {@link DetectedPhrase.Builder} with state corresponding to
+     * this instance.
+     * @hide
+     */
+    public DetectedPhrase.Builder buildUpon() {
+        return new DetectedPhrase.Builder()
+                .setId(mId)
+                .setPhrase(mPhrase);
+    }
+
+    private void onConstructed() {
+        Preconditions.checkArgumentNonnegative(mId, "hotwordPhraseId");
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/DetectedPhrase.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ DetectedPhrase(
+            int id,
+            @Nullable String phrase) {
+        this.mId = id;
+        this.mPhrase = phrase;
+
+        onConstructed();
+    }
+
+    /**
+     * An ID representing the keyphrase that triggered the successful detection.
+     */
+    @DataClass.Generated.Member
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * A string representing exactly what was heard and interpreted by the service leading to
+     * a successful detection.
+     *
+     * <p>Can be null if not set in {@link DetectedPhrase.Builder}
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getPhrase() {
+        return mPhrase;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DetectedPhrase { " +
+                "id = " + mId + ", " +
+                "phrase = " + mPhrase +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(DetectedPhrase other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        DetectedPhrase that = (DetectedPhrase) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mId == that.mId
+                && java.util.Objects.equals(mPhrase, that.mPhrase);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mId;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mPhrase);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mPhrase != null) flg |= 0x2;
+        dest.writeByte(flg);
+        dest.writeInt(mId);
+        if (mPhrase != null) dest.writeString(mPhrase);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ DetectedPhrase(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        int id = in.readInt();
+        String phrase = (flg & 0x2) == 0 ? null : in.readString();
+
+        this.mId = id;
+        this.mPhrase = phrase;
+
+        onConstructed();
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<DetectedPhrase> CREATOR
+            = new Parcelable.Creator<DetectedPhrase>() {
+        @Override
+        public DetectedPhrase[] newArray(int size) {
+            return new DetectedPhrase[size];
+        }
+
+        @Override
+        public DetectedPhrase createFromParcel(@NonNull android.os.Parcel in) {
+            return new DetectedPhrase(in);
+        }
+    };
+
+    /**
+     * A builder for {@link DetectedPhrase}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private int mId;
+        private @Nullable String mPhrase;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * An ID representing the keyphrase that triggered the successful detection.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setId(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mId = value;
+            return this;
+        }
+
+        /**
+         * A string representing exactly what was heard and interpreted by the service leading to
+         * a successful detection.
+         *
+         * <p>Can be null if not set in {@link DetectedPhrase.Builder}
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setPhrase(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mPhrase = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull DetectedPhrase build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mId = 0;
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mPhrase = null;
+            }
+            DetectedPhrase o = new DetectedPhrase(
+                    mId,
+                    mPhrase);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1676870329959L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/service/voice/DetectedPhrase.java",
+            inputSignatures = "private  int mId\nprivate @android.annotation.Nullable java.lang.String mPhrase\nstatic  int defaultHotwordPhraseId()\npublic  android.service.voice.DetectedPhrase.Builder buildUpon()\nprivate  void onConstructed()\nclass DetectedPhrase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index dee560b..dd3f99c 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -203,17 +203,23 @@
      * An ID representing the keyphrase that triggered the successful detection.
      *
      * <p>Only values between 0 and {@link #getMaxHotwordPhraseId()} (inclusive) are accepted.
+     *
+     * @deprecated Use {@link #getDetectedPhrase()} and
+     *     {@link DetectedPhrase#getId()}.
      */
-    private final int mHotwordPhraseId;
-    private static int defaultHotwordPhraseId() {
-        return 0;
+    @Deprecated
+    public int getHotwordPhraseId() {
+        return mDetectedPhrase.getId();
     }
 
     /**
      * Returns the maximum value of {@link #getHotwordPhraseId()}.
+     *
+     * @deprecated There is no maximum phrase ID enforced
      */
+    @Deprecated
     public static int getMaxHotwordPhraseId() {
-        return 63;
+        return Integer.MAX_VALUE;
     }
 
     /**
@@ -285,6 +291,10 @@
         return mMediaSyncEvent;
     }
 
+    @NonNull
+    private DetectedPhrase mDetectedPhrase =
+            new DetectedPhrase.Builder().build();
+
     /**
      * Returns how many bytes should be written into the Parcel
      *
@@ -302,6 +312,9 @@
     /**
      * Returns how many bits have been written into the HotwordDetectedResult.
      *
+     * <p>{@link #getAudioStreams()} and {@link #getDetectedPhrase()}
+     * are not counted here.
+     *
      * @hide
      */
     public static int getUsageSize(@NonNull HotwordDetectedResult hotwordDetectedResult) {
@@ -329,9 +342,6 @@
         if (hotwordDetectedResult.getPersonalizedScore() != defaultPersonalizedScore()) {
             totalBits += bitCount(HotwordDetectedResult.getMaxScore());
         }
-        if (hotwordDetectedResult.getHotwordPhraseId() != defaultHotwordPhraseId()) {
-            totalBits += bitCount(HotwordDetectedResult.getMaxHotwordPhraseId());
-        }
         PersistableBundle persistableBundle = hotwordDetectedResult.getExtras();
         if (!persistableBundle.isEmpty()) {
             totalBits += getParcelableSize(persistableBundle) * Byte.SIZE;
@@ -339,7 +349,7 @@
         return totalBits;
     }
 
-    private static int bitCount(long value) {
+    static int bitCount(long value) {
         int bits = 0;
         while (value > 0) {
             bits++;
@@ -352,8 +362,6 @@
         Preconditions.checkArgumentInRange(mScore, 0, getMaxScore(), "score");
         Preconditions.checkArgumentInRange(mPersonalizedScore, 0, getMaxScore(),
                 "personalizedScore");
-        Preconditions.checkArgumentInRange(mHotwordPhraseId, 0, getMaxHotwordPhraseId(),
-                "hotwordPhraseId");
         Preconditions.checkArgumentInRange((long) mHotwordDurationMillis, 0,
                 AudioRecord.getMaxSharedAudioHistoryMillis(), "hotwordDurationMillis");
         if (mHotwordOffsetMillis != HOTWORD_OFFSET_UNSET) {
@@ -448,10 +456,25 @@
             Objects.requireNonNull(value, "value should not be null");
             final Builder builder = (Builder) this;
             // If the code gen flag in build() is changed, we must update the flag e.g. 0x200 here.
-            builder.mBuilderFieldsSet |= 0x200;
+            builder.mBuilderFieldsSet |= 0x100;
             builder.mAudioStreams = List.copyOf(value);
             return builder;
         }
+
+        /**
+         * An ID representing the keyphrase that triggered the successful detection.
+         *
+         * <p>Only values between 0 and {@link #getMaxHotwordPhraseId()} (inclusive) are accepted.
+         *
+         * @deprecated Use {@link HotwordDetectedResult.Builder#setDetectedPhrase(DetectedPhrase)}
+         *      and {@link DetectedPhrase.Builder#setId(int)}
+         */
+        @Deprecated
+        public @NonNull Builder setHotwordPhraseId(int value) {
+            final Builder builder = (Builder) this;
+            builder.setDetectedPhrase(new DetectedPhrase.Builder().setId(value).build());
+            return builder;
+        }
     }
 
     /**
@@ -468,9 +491,9 @@
             .setHotwordDetectionPersonalized(mHotwordDetectionPersonalized)
             .setScore(mScore)
             .setPersonalizedScore(mPersonalizedScore)
-            .setHotwordPhraseId(mHotwordPhraseId)
             .setAudioStreams(mAudioStreams)
-            .setExtras(mExtras);
+            .setExtras(mExtras)
+            .setDetectedPhrase(mDetectedPhrase);
     }
 
 
@@ -579,9 +602,9 @@
             boolean hotwordDetectionPersonalized,
             int score,
             int personalizedScore,
-            int hotwordPhraseId,
             @NonNull List<HotwordAudioStream> audioStreams,
-            @NonNull PersistableBundle extras) {
+            @NonNull PersistableBundle extras,
+            @NonNull DetectedPhrase detectedPhrase) {
         this.mConfidenceLevel = confidenceLevel;
         com.android.internal.util.AnnotationValidations.validate(
                 HotwordConfidenceLevelValue.class, null, mConfidenceLevel);
@@ -592,13 +615,15 @@
         this.mHotwordDetectionPersonalized = hotwordDetectionPersonalized;
         this.mScore = score;
         this.mPersonalizedScore = personalizedScore;
-        this.mHotwordPhraseId = hotwordPhraseId;
         this.mAudioStreams = audioStreams;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mAudioStreams);
         this.mExtras = extras;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mExtras);
+        this.mDetectedPhrase = detectedPhrase;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDetectedPhrase);
 
         onConstructed();
     }
@@ -673,16 +698,6 @@
     }
 
     /**
-     * An ID representing the keyphrase that triggered the successful detection.
-     *
-     * <p>Only values between 0 and {@link #getMaxHotwordPhraseId()} (inclusive) are accepted.
-     */
-    @DataClass.Generated.Member
-    public int getHotwordPhraseId() {
-        return mHotwordPhraseId;
-    }
-
-    /**
      * App-specific extras to support trigger.
      *
      * <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -712,6 +727,11 @@
         return mExtras;
     }
 
+    @DataClass.Generated.Member
+    public @NonNull DetectedPhrase getDetectedPhrase() {
+        return mDetectedPhrase;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -727,9 +747,9 @@
                 "hotwordDetectionPersonalized = " + mHotwordDetectionPersonalized + ", " +
                 "score = " + mScore + ", " +
                 "personalizedScore = " + mPersonalizedScore + ", " +
-                "hotwordPhraseId = " + mHotwordPhraseId + ", " +
                 "audioStreams = " + mAudioStreams + ", " +
-                "extras = " + mExtras +
+                "extras = " + mExtras + ", " +
+                "detectedPhrase = " + mDetectedPhrase +
         " }";
     }
 
@@ -754,9 +774,9 @@
                 && mHotwordDetectionPersonalized == that.mHotwordDetectionPersonalized
                 && mScore == that.mScore
                 && mPersonalizedScore == that.mPersonalizedScore
-                && mHotwordPhraseId == that.mHotwordPhraseId
                 && Objects.equals(mAudioStreams, that.mAudioStreams)
-                && Objects.equals(mExtras, that.mExtras);
+                && Objects.equals(mExtras, that.mExtras)
+                && Objects.equals(mDetectedPhrase, that.mDetectedPhrase);
     }
 
     @Override
@@ -774,9 +794,9 @@
         _hash = 31 * _hash + Boolean.hashCode(mHotwordDetectionPersonalized);
         _hash = 31 * _hash + mScore;
         _hash = 31 * _hash + mPersonalizedScore;
-        _hash = 31 * _hash + mHotwordPhraseId;
         _hash = 31 * _hash + Objects.hashCode(mAudioStreams);
         _hash = 31 * _hash + Objects.hashCode(mExtras);
+        _hash = 31 * _hash + Objects.hashCode(mDetectedPhrase);
         return _hash;
     }
 
@@ -797,9 +817,9 @@
         dest.writeInt(mAudioChannel);
         dest.writeInt(mScore);
         dest.writeInt(mPersonalizedScore);
-        dest.writeInt(mHotwordPhraseId);
         dest.writeParcelableList(mAudioStreams, flags);
         dest.writeTypedObject(mExtras, flags);
+        dest.writeTypedObject(mDetectedPhrase, flags);
     }
 
     @Override
@@ -822,10 +842,10 @@
         int audioChannel = in.readInt();
         int score = in.readInt();
         int personalizedScore = in.readInt();
-        int hotwordPhraseId = in.readInt();
         List<HotwordAudioStream> audioStreams = new ArrayList<>();
         in.readParcelableList(audioStreams, HotwordAudioStream.class.getClassLoader());
         PersistableBundle extras = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
+        DetectedPhrase detectedPhrase = (DetectedPhrase) in.readTypedObject(DetectedPhrase.CREATOR);
 
         this.mConfidenceLevel = confidenceLevel;
         com.android.internal.util.AnnotationValidations.validate(
@@ -837,13 +857,15 @@
         this.mHotwordDetectionPersonalized = hotwordDetectionPersonalized;
         this.mScore = score;
         this.mPersonalizedScore = personalizedScore;
-        this.mHotwordPhraseId = hotwordPhraseId;
         this.mAudioStreams = audioStreams;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mAudioStreams);
         this.mExtras = extras;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mExtras);
+        this.mDetectedPhrase = detectedPhrase;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDetectedPhrase);
 
         onConstructed();
     }
@@ -877,9 +899,9 @@
         private boolean mHotwordDetectionPersonalized;
         private int mScore;
         private int mPersonalizedScore;
-        private int mHotwordPhraseId;
         private @NonNull List<HotwordAudioStream> mAudioStreams;
         private @NonNull PersistableBundle mExtras;
+        private @NonNull DetectedPhrase mDetectedPhrase;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -990,19 +1012,6 @@
         }
 
         /**
-         * An ID representing the keyphrase that triggered the successful detection.
-         *
-         * <p>Only values between 0 and {@link #getMaxHotwordPhraseId()} (inclusive) are accepted.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setHotwordPhraseId(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x100;
-            mHotwordPhraseId = value;
-            return this;
-        }
-
-        /**
          * App-specific extras to support trigger.
          *
          * <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -1030,11 +1039,19 @@
         @DataClass.Generated.Member
         public @NonNull Builder setExtras(@NonNull PersistableBundle value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400;
+            mBuilderFieldsSet |= 0x200;
             mExtras = value;
             return this;
         }
 
+        @DataClass.Generated.Member
+        public @NonNull Builder setDetectedPhrase(@NonNull DetectedPhrase value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x400;
+            mDetectedPhrase = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull HotwordDetectedResult build() {
             checkNotUsed();
@@ -1065,14 +1082,14 @@
                 mPersonalizedScore = defaultPersonalizedScore();
             }
             if ((mBuilderFieldsSet & 0x100) == 0) {
-                mHotwordPhraseId = defaultHotwordPhraseId();
-            }
-            if ((mBuilderFieldsSet & 0x200) == 0) {
                 mAudioStreams = defaultAudioStreams();
             }
-            if ((mBuilderFieldsSet & 0x400) == 0) {
+            if ((mBuilderFieldsSet & 0x200) == 0) {
                 mExtras = defaultExtras();
             }
+            if ((mBuilderFieldsSet & 0x400) == 0) {
+                mDetectedPhrase = new DetectedPhrase.Builder().build();
+            }
             HotwordDetectedResult o = new HotwordDetectedResult(
                     mConfidenceLevel,
                     mMediaSyncEvent,
@@ -1082,9 +1099,9 @@
                     mHotwordDetectionPersonalized,
                     mScore,
                     mPersonalizedScore,
-                    mHotwordPhraseId,
                     mAudioStreams,
-                    mExtras);
+                    mExtras,
+                    mDetectedPhrase);
             return o;
         }
 
@@ -1097,10 +1114,10 @@
     }
 
     @DataClass.Generated(
-            time = 1668385264834L,
+            time = 1676870324215L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate @android.annotation.NonNull android.service.voice.DetectedPhrase mDetectedPhrase\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\npublic @java.lang.Deprecated int getHotwordPhraseId()\npublic static @java.lang.Deprecated int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nstatic  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index eb36a70..a746dc6 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, "true");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
+        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_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/WindowManager.java b/core/java/android/view/WindowManager.java
index 725eb51..e7cefd6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -108,6 +108,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
@@ -1105,6 +1106,26 @@
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
 
     /**
+     * Whether the device supports the WindowManager Extensions.
+     * OEMs can enable this by having their device config to inherit window_extensions.mk, such as:
+     * <pre>
+     * $(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk)
+     * </pre>
+     * @hide
+     */
+    boolean WINDOW_EXTENSIONS_ENABLED =
+            SystemProperties.getBoolean("persist.wm.extensions.enabled", false);
+
+    /**
+     * @see #WINDOW_EXTENSIONS_ENABLED
+     * @hide
+     */
+    @TestApi
+    static boolean hasWindowExtensionsEnabled() {
+        return WINDOW_EXTENSIONS_ENABLED;
+    }
+
+    /**
      * Application-level
      * {@link android.content.pm.PackageManager.Property PackageManager.Property}
      * tag that specifies whether OEMs are permitted to provide activity
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 59b5286..34c7b8b 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -82,11 +82,19 @@
     @SystemApi
     public static final int FLAG_RECONNECTED = 0x4;
 
+    /**
+     * Flag used to disable flush when receiving a VIEW_TREE_APPEARING event.
+     *
+     * @hide
+     */
+    public static final int FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING = 1 << 3;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             FLAG_DISABLED_BY_APP,
             FLAG_DISABLED_BY_FLAG_SECURE,
-            FLAG_RECONNECTED
+            FLAG_RECONNECTED,
+            FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ContextCreationFlags{}
@@ -252,7 +260,8 @@
      * Gets the flags associated with this context.
      *
      * @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE},
-     * {@link #FLAG_DISABLED_BY_APP} and {@link #FLAG_RECONNECTED}.
+     * {@link #FLAG_DISABLED_BY_APP}, {@link #FLAG_RECONNECTED} and {@link
+     * #FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING}.
      *
      * @hide
      */
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 497f066..668351b 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -51,6 +51,7 @@
 import android.view.contentcapture.ContentCaptureSession.FlushReason;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.SyncResultReceiver;
 
 import java.io.PrintWriter;
@@ -343,6 +344,14 @@
      */
     public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
 
+    /**
+     * Sets to disable flush when receiving a VIEW_TREE_APPEARING event.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
+            "disable_flush_for_view_tree_appearing";
+
     /** @hide */
     @TestApi
     public static final int LOGGING_LEVEL_OFF = 0;
@@ -373,6 +382,8 @@
     public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
     /** @hide */
     public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
+    /** @hide */
+    public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
 
     private final Object mLock = new Object();
 
@@ -448,6 +459,7 @@
         mOptions = Objects.requireNonNull(options, "options cannot be null");
 
         ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
+        setFlushViewTreeAppearingEventDisabled(mOptions.disableFlushForViewTreeAppearing);
 
         if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
 
@@ -687,6 +699,38 @@
     }
 
     /**
+     * Explicitly sets enable or disable flush for view tree appearing event.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public void setFlushViewTreeAppearingEventDisabled(boolean disabled) {
+        if (sDebug) {
+            Log.d(TAG, "setFlushViewTreeAppearingEventDisabled(): setting to " + disabled);
+        }
+
+        synchronized (mLock) {
+            if (disabled) {
+                mFlags |= ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
+            } else {
+                mFlags &= ~ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
+            }
+        }
+    }
+
+    /**
+     * Gets whether content capture is needed to flush for view tree appearing event.
+     *
+     * @hide
+     */
+    public boolean getFlushViewTreeAppearingEventDisabled() {
+        synchronized (mLock) {
+            return (mFlags & ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING)
+                    != 0;
+        }
+    }
+
+    /**
      * Gets whether content capture is enabled for the given user.
      *
      * <p>This method is typically used by the content capture service settings page, so it can
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 0da7e3b..b7f03e1 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -177,6 +177,10 @@
     public static final int FLUSH_REASON_SESSION_CONNECTED = 7;
     /** @hide */
     public static final int FLUSH_REASON_FORCE_FLUSH = 8;
+    /** @hide */
+    public static final int FLUSH_REASON_VIEW_TREE_APPEARING = 9;
+    /** @hide */
+    public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
 
     @ChangeId
     @EnabledSince(targetSdkVersion = UPSIDE_DOWN_CAKE)
@@ -191,7 +195,9 @@
             FLUSH_REASON_IDLE_TIMEOUT,
             FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
             FLUSH_REASON_SESSION_CONNECTED,
-            FLUSH_REASON_FORCE_FLUSH
+            FLUSH_REASON_FORCE_FLUSH,
+            FLUSH_REASON_VIEW_TREE_APPEARING,
+            FLUSH_REASON_VIEW_TREE_APPEARED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FlushReason{}
@@ -671,6 +677,10 @@
                 return "CONNECTED";
             case FLUSH_REASON_FORCE_FLUSH:
                 return "FORCE_FLUSH";
+            case FLUSH_REASON_VIEW_TREE_APPEARING:
+                return "VIEW_TREE_APPEARING";
+            case FLUSH_REASON_VIEW_TREE_APPEARED:
+                return "VIEW_TREE_APPEARED";
             default:
                 return "UNKOWN-" + reason;
         }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 8c040e4..6ddfcb8 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -458,6 +458,12 @@
             case ContentCaptureEvent.TYPE_SESSION_FINISHED:
                 flushReason = FLUSH_REASON_SESSION_FINISHED;
                 break;
+            case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING:
+                flushReason = FLUSH_REASON_VIEW_TREE_APPEARING;
+                break;
+            case ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED:
+                flushReason = FLUSH_REASON_VIEW_TREE_APPEARED;
+                break;
             default:
                 flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL;
         }
@@ -774,7 +780,11 @@
     /** Public because is also used by ViewRootImpl */
     public void notifyViewTreeEvent(int sessionId, boolean started) {
         final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
-        mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH));
+        final boolean disableFlush = mManager.getFlushViewTreeAppearingEventDisabled();
+
+        mHandler.post(() -> sendEvent(
+                new ContentCaptureEvent(sessionId, type),
+                disableFlush ? !started : FORCE_FLUSH));
     }
 
     void notifySessionResumed(int sessionId) {
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 57e0ce8..534c9de 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -63,7 +63,7 @@
      * @param transitionToken A token associated with the transition to start.
      * @param t Operations that are part of the transition.
      */
-    oneway void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
+    void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
 
     /**
      * Starts a legacy transition.
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index c19abf9..5dbf328 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -53,25 +53,12 @@
     private final IBinder mOwnerToken;
 
     /**
-     * The initial bounds of the TaskFragment. Fills parent if empty.
-     * TODO(b/232476698): cleanup with update CTS.
-     */
-    @NonNull
-    private final Rect mInitialBounds = new Rect();
-
-    /**
      * The initial relative bounds of the TaskFragment in parent coordinate.
      * Fills parent if empty.
      */
     @NonNull
     private final Rect mInitialRelativeBounds = new Rect();
 
-    /**
-     * Whether the params are using {@link Builder#setInitialRelativeBounds(Rect)}.
-     * TODO(b/232476698): remove after remove mInitialBounds
-     */
-    private final boolean mAreInitialRelativeBoundsSet;
-
     /** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */
     @WindowingMode
     private final int mWindowingMode;
@@ -109,8 +96,7 @@
 
     private TaskFragmentCreationParams(
             @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
-            @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
-            @NonNull Rect initialRelativeBounds, boolean areInitialRelativeBoundsSet,
+            @NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
             @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
             @Nullable IBinder pairedActivityToken) {
         if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
@@ -120,9 +106,7 @@
         mOrganizer = organizer;
         mFragmentToken = fragmentToken;
         mOwnerToken = ownerToken;
-        mInitialBounds.set(initialBounds);
         mInitialRelativeBounds.set(initialRelativeBounds);
-        mAreInitialRelativeBoundsSet = areInitialRelativeBoundsSet;
         mWindowingMode = windowingMode;
         mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
         mPairedActivityToken = pairedActivityToken;
@@ -144,27 +128,10 @@
     }
 
     @NonNull
-    public Rect getInitialBounds() {
-        return mInitialBounds;
-    }
-
-    /**
-     * TODO(b/232476698): remove the hide with adding CTS for this in next release.
-     * @hide
-     */
-    @NonNull
     public Rect getInitialRelativeBounds() {
         return mInitialRelativeBounds;
     }
 
-    /**
-     * TODO(b/232476698): remove after remove mInitialBounds.
-     * @hide
-     */
-    public boolean areInitialRelativeBoundsSet() {
-        return mAreInitialRelativeBoundsSet;
-    }
-
     @WindowingMode
     public int getWindowingMode() {
         return mWindowingMode;
@@ -192,9 +159,7 @@
         mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
         mOwnerToken = in.readStrongBinder();
-        mInitialBounds.readFromParcel(in);
         mInitialRelativeBounds.readFromParcel(in);
-        mAreInitialRelativeBoundsSet = in.readBoolean();
         mWindowingMode = in.readInt();
         mPairedPrimaryFragmentToken = in.readStrongBinder();
         mPairedActivityToken = in.readStrongBinder();
@@ -206,9 +171,7 @@
         mOrganizer.writeToParcel(dest, flags);
         dest.writeStrongBinder(mFragmentToken);
         dest.writeStrongBinder(mOwnerToken);
-        mInitialBounds.writeToParcel(dest, flags);
         mInitialRelativeBounds.writeToParcel(dest, flags);
-        dest.writeBoolean(mAreInitialRelativeBoundsSet);
         dest.writeInt(mWindowingMode);
         dest.writeStrongBinder(mPairedPrimaryFragmentToken);
         dest.writeStrongBinder(mPairedActivityToken);
@@ -234,7 +197,6 @@
                 + " organizer=" + mOrganizer
                 + " fragmentToken=" + mFragmentToken
                 + " ownerToken=" + mOwnerToken
-                + " initialBounds=" + mInitialBounds
                 + " initialRelativeBounds=" + mInitialRelativeBounds
                 + " windowingMode=" + mWindowingMode
                 + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
@@ -261,13 +223,8 @@
         private final IBinder mOwnerToken;
 
         @NonNull
-        private final Rect mInitialBounds = new Rect();
-
-        @NonNull
         private final Rect mInitialRelativeBounds = new Rect();
 
-        private boolean mAreInitialRelativeBoundsSet;
-
         @WindowingMode
         private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
 
@@ -284,23 +241,13 @@
             mOwnerToken = ownerToken;
         }
 
-        /** Sets the initial bounds for the TaskFragment. */
-        @NonNull
-        public Builder setInitialBounds(@NonNull Rect bounds) {
-            mInitialBounds.set(bounds);
-            return this;
-        }
-
         /**
          * Sets the initial relative bounds for the TaskFragment in parent coordinate.
          * Set to empty to fill parent.
-         * TODO(b/232476698): remove the hide with adding CTS for this in next release.
-         * @hide
          */
         @NonNull
         public Builder setInitialRelativeBounds(@NonNull Rect bounds) {
             mInitialRelativeBounds.set(bounds);
-            mAreInitialRelativeBoundsSet = true;
             return this;
         }
 
@@ -355,8 +302,8 @@
         @NonNull
         public TaskFragmentCreationParams build() {
             return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
-                    mInitialBounds, mInitialRelativeBounds, mAreInitialRelativeBoundsSet,
-                    mWindowingMode, mPairedPrimaryFragmentToken, mPairedActivityToken);
+                    mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
+                    mPairedActivityToken);
         }
     }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c833553..16db818 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7564,7 +7564,7 @@
          HealthConnect can later integrate it. -->
     <permission android:name="android.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA"
                 android:protectionLevel="signature|knownSigner"
-                android:knownCerts="@array/config_healthConnectStagingDataKnownSigners"/>
+                android:knownCerts="@array/config_healthConnectRestoreKnownSigners"/>
 
     <!-- @hide @TestApi Allows an application to clear HealthConnect's staged remote data for
          testing only. For security reasons, this is a platform-only permission. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5d78927..12eff67 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6286,7 +6286,7 @@
     <!-- Certificate digests for trusted apps that will be allowed to obtain the knownSigner
          permission for staging HealthConnect's remote data. The digest should be computed over the
          DER encoding of the trusted certificate using the SHA-256 digest algorithm. -->
-    <string-array name="config_healthConnectStagingDataKnownSigners">
+    <string-array name="config_healthConnectRestoreKnownSigners">
     </string-array>
     <!-- Certificate digests for trusted apps that will be allowed to obtain the knownSigner Health
         Connect Migration permissions. The digest should be computed over the DER encoding of the
diff --git a/core/tests/companiontests/OWNERS b/core/tests/companiontests/OWNERS
new file mode 100644
index 0000000..734d8b6
--- /dev/null
+++ b/core/tests/companiontests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/companion/OWNERS
\ No newline at end of file
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index d633843..2b4123a 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -60,6 +60,7 @@
         mContext = getInstrumentation().getTargetContext();
         mCdm = mContext.getSystemService(CompanionDeviceManager.class);
         mAssociationId = createAssociation();
+        mCdm.enableSecureTransport(false);
     }
 
     @Override
@@ -67,6 +68,7 @@
         super.tearDown();
 
         mCdm.disassociate(mAssociationId);
+        mCdm.enableSecureTransport(true);
     }
 
     public void testPingHandRolled() {
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index eae1bbc..17ed4c4 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,6 +15,8 @@
  */
 package android.view.contentcapture;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 
@@ -54,4 +56,19 @@
 
         assertThrows(NullPointerException.class, () -> manager.removeData(null));
     }
+
+    @Test
+    @SuppressWarnings("GuardedBy")
+    public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
+        final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
+        final ContentCaptureOptions options = new ContentCaptureOptions(null);
+        final ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mockService, options);
+
+        assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
+        manager.setFlushViewTreeAppearingEventDisabled(true);
+        assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isTrue();
+        manager.setFlushViewTreeAppearingEventDisabled(false);
+        assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
+    }
 }
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index ff8d96d..421bc25 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -102,11 +102,29 @@
 to pre-existing users, but cannot uninstall pre-existing system packages from pre-existing users.
 -->
 <config>
+    <!--  Bluetooth (com.android.btservices apex) - visible on the sharesheet -->
+    <install-in-user-type package="com.android.bluetooth">
+        <install-in user-type="SYSTEM" />
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+        <do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+    </install-in-user-type>
+
+    <!--  Settings (Settings app) -->
+    <install-in-user-type package="com.android.settings">
+        <install-in user-type="SYSTEM" />
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+
+    <!-- Settings Storage (SettingsProvider)  -->
     <install-in-user-type package="com.android.providers.settings">
         <install-in user-type="SYSTEM" />
         <install-in user-type="FULL" />
         <install-in user-type="PROFILE" />
     </install-in-user-type>
+
+    <!-- WallpaperBackup (WallpaperBackup)-->
     <install-in-user-type package="com.android.wallpaperbackup">
         <install-in user-type="FULL" />
     </install-in-user-type>
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index 470a06c..a7c508c 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -96,8 +96,8 @@
             throw new RuntimeException("internal error: native gainmap is 0");
         }
 
-        mGainmapContents = gainmapContents;
         mNativePtr = nativeGainmap;
+        setGainmapContents(gainmapContents);
 
         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativeGainmap);
     }
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index babcfc3..67eb117 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Px;
+import android.os.Trace;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
@@ -475,19 +476,26 @@
             @NonNull MeasuredText measuredPara,
             @NonNull ParagraphConstraints constraints,
             @IntRange(from = 0) int lineNumber) {
-        return new Result(nComputeLineBreaks(
-                mNativePtr,
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "compute line break");
+        long result = 0;
+        try {
+            result = nComputeLineBreaks(
+                    mNativePtr,
 
-                // Inputs
-                measuredPara.getChars(),
-                measuredPara.getNativePtr(),
-                measuredPara.getChars().length,
-                constraints.mFirstWidth,
-                constraints.mFirstWidthLineCount,
-                constraints.mWidth,
-                constraints.mVariableTabStops,
-                constraints.mDefaultTabStop,
-                lineNumber));
+                    // Inputs
+                    measuredPara.getChars(),
+                    measuredPara.getNativePtr(),
+                    measuredPara.getChars().length,
+                    constraints.mFirstWidth,
+                    constraints.mFirstWidthLineCount,
+                    constraints.mWidth,
+                    constraints.mVariableTabStops,
+                    constraints.mDefaultTabStop,
+                    lineNumber);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+        return new Result(result);
     }
 
     @FastNative
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 0f45219..c7c9424 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -49,6 +49,7 @@
         "src/com/android/wm/shell/animation/Interpolators.java",
         "src/com/android/wm/shell/pip/PipContentOverlay.java",
         "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
+        "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
     ],
     path: "src",
 }
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml
deleted file mode 100644
index 0d88113..0000000
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid android:color="@color/letterbox_education_accent_primary"/>
-    <corners android:radius="12dp"/>
-</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
index 42572d6..a269968 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml
@@ -14,7 +14,30 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/letterbox_education_dismiss_button_background_ripple">
-    <item android:drawable="@drawable/letterbox_education_dismiss_button_background"/>
-</ripple>
\ No newline at end of file
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetTop="@dimen/letterbox_education_dialog_vertical_inset"
+       android:insetBottom="@dimen/letterbox_education_dialog_vertical_inset">
+    <ripple android:color="@color/letterbox_education_dismiss_button_background_ripple">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <corners android:radius="@dimen/letterbox_education_dialog_button_radius"/>
+                <solid android:color="@android:color/white"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/transparent"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <solid android:color="@color/letterbox_education_accent_primary"/>
+                <corners android:radius="@dimen/letterbox_education_dialog_button_radius"/>
+                <padding android:left="@dimen/letterbox_education_dialog_horizontal_padding"
+                         android:top="@dimen/letterbox_education_dialog_vertical_padding"
+                         android:right="@dimen/letterbox_education_dialog_horizontal_padding"
+                         android:bottom="@dimen/letterbox_education_dialog_vertical_padding"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml
deleted file mode 100644
index 60f3cfe..0000000
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
-    <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
-</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
index ef97ea1..1f12514 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
@@ -14,7 +14,31 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/letterbox_restart_button_background_ripple">
-    <item android:drawable="@drawable/letterbox_restart_button_background"/>
-</ripple>
\ No newline at end of file
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:insetTop="@dimen/letterbox_restart_dialog_vertical_inset"
+       android:insetBottom="@dimen/letterbox_restart_dialog_vertical_inset">
+    <ripple android:color="@color/letterbox_restart_dismiss_button_background_ripple">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+                <solid android:color="@android:color/white"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/transparent"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+                <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+                <padding android:left="@dimen/letterbox_restart_dialog_horizontal_padding"
+                         android:top="@dimen/letterbox_restart_dialog_vertical_padding"
+                         android:right="@dimen/letterbox_restart_dialog_horizontal_padding"
+                         android:bottom="@dimen/letterbox_restart_dialog_vertical_padding"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml
deleted file mode 100644
index af89d41..0000000
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" android:width="1dp"/>
-    <solid android:color="?androidprv:attr/colorSurface" />
-    <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
-</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
index e32aefc..3aa0981 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
@@ -14,7 +14,33 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/letterbox_restart_dismiss_button_background_ripple">
-    <item android:drawable="@drawable/letterbox_restart_dismiss_button_background"/>
-</ripple>
\ No newline at end of file
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:insetTop="@dimen/letterbox_restart_dialog_vertical_inset"
+       android:insetBottom="@dimen/letterbox_restart_dialog_vertical_inset">
+    <ripple android:color="@color/letterbox_restart_dismiss_button_background_ripple">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+                <solid android:color="@android:color/white"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/transparent"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+                        android:width="1dp"/>
+                <solid android:color="?androidprv:attr/colorSurface"/>
+                <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+                <padding android:left="@dimen/letterbox_restart_dialog_horizontal_padding"
+                         android:top="@dimen/letterbox_restart_dialog_vertical_padding"
+                         android:right="@dimen/letterbox_restart_dialog_horizontal_padding"
+                         android:bottom="@dimen/letterbox_restart_dialog_vertical_padding"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
index 44b2f45..3d3c003 100644
--- a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
@@ -29,11 +29,15 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:lineSpacingExtra="4sp"
+        android:letterSpacing="0.02"
         android:background="@drawable/compat_hint_bubble"
         android:padding="16dp"
         android:textAlignment="viewStart"
         android:textColor="@color/compat_controls_text"
-        android:textSize="14sp"/>
+        android:textSize="14sp"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead"
+    />
 
     <ImageView
         android:layout_width="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
index c65f24d..095576b 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
@@ -29,6 +29,8 @@
         android:layout_marginBottom="20dp"/>
 
     <TextView
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
         android:id="@+id/letterbox_education_dialog_action_text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
index 3a44eb9..e8edad1 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -69,6 +69,8 @@
                     android:text="@string/letterbox_education_dialog_title"
                     android:textAlignment="center"
                     android:textColor="@color/compat_controls_text"
+                    android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"
                     android:textSize="24sp"/>
 
                 <LinearLayout
@@ -95,10 +97,16 @@
                 </LinearLayout>
 
                 <Button
+                    android:fontFamily="@*android:string/config_bodyFontFamily"
+                    android:fontWeight="500"
+                    android:lineHeight="20dp"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Small"
                     android:id="@+id/letterbox_education_dialog_dismiss_button"
+                    android:textStyle="bold"
                     android:layout_width="match_parent"
                     android:layout_height="56dp"
                     android:layout_marginTop="40dp"
+                    android:textSize="14sp"
                     android:background=
                         "@drawable/letterbox_education_dismiss_button_background_ripple"
                     android:text="@string/letterbox_education_got_it"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1f9b6cf..336c156 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -273,6 +273,18 @@
     <!-- The space between two actions in the letterbox education dialog -->
     <dimen name="letterbox_education_dialog_space_between_actions">24dp</dimen>
 
+    <!-- The corner radius of the buttons in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_button_radius">12dp</dimen>
+
+    <!-- The horizontal padding for the buttons in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_horizontal_padding">16dp</dimen>
+
+    <!-- The vertical padding for the buttons in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_vertical_padding">8dp</dimen>
+
+    <!-- The insets for the buttons in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_vertical_inset">6dp</dimen>
+
     <!-- The margin between the dialog container and its parent. -->
     <dimen name="letterbox_restart_dialog_margin">24dp</dimen>
 
@@ -306,6 +318,15 @@
     <!-- The corner radius of the buttons in the restart dialog -->
     <dimen name="letterbox_restart_dialog_button_radius">18dp</dimen>
 
+    <!-- The insets for the buttons in the letterbox restart dialog -->
+    <dimen name="letterbox_restart_dialog_vertical_inset">6dp</dimen>
+
+    <!-- The horizontal padding for the buttons in the letterbox restart dialog -->
+    <dimen name="letterbox_restart_dialog_horizontal_padding">16dp</dimen>
+
+    <!-- The vertical padding for the buttons in the letterbox restart dialog -->
+    <dimen name="letterbox_restart_dialog_vertical_padding">8dp</dimen>
+
     <!-- The width of the brand image on staring surface. -->
     <dimen name="starting_surface_brand_image_width">200dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index bae009a..0a0c49f 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -87,6 +87,9 @@
         <item name="android:textAppearance">
             @*android:style/TextAppearance.DeviceDefault.Headline
         </item>
+        <item name="android:fontFamily">
+            @*android:string/config_bodyFontFamilyMedium
+        </item>
     </style>
 
     <style name="RestartDialogBodyText">
@@ -97,24 +100,44 @@
         <item name="android:textAppearance">
             @*android:style/TextAppearance.DeviceDefault.Body2
         </item>
+        <item name="android:fontFamily">
+            @*android:string/config_bodyFontFamily
+        </item>
     </style>
 
     <style name="RestartDialogCheckboxText">
         <item name="android:textSize">16sp</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:lineSpacingExtra">4sp</item>
-        <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault</item>
+        <item name="android:textAppearance">
+            @*android:style/TextAppearance.DeviceDefault.Headline
+        </item>
+        <item name="android:fontFamily">
+            @*android:string/config_bodyFontFamilyMedium
+        </item>
     </style>
 
     <style name="RestartDialogDismissButton">
         <item name="android:lineSpacingExtra">2sp</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textAppearance">
+            @*android:style/TextAppearance.DeviceDefault.Body2
+        </item>
+        <item name="android:fontFamily">
+            @*android:string/config_bodyFontFamily
+        </item>
     </style>
 
     <style name="RestartDialogConfirmButton">
         <item name="android:lineSpacingExtra">2sp</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textAppearance">
+            @*android:style/TextAppearance.DeviceDefault.Body2
+        </item>
+        <item name="android:fontFamily">
+            @*android:string/config_bodyFontFamily
+        </item>
     </style>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ee5d205..acf17e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -403,7 +403,7 @@
                 InputDevice.SOURCE_KEYBOARD);
 
         ev.setDisplayId(mContext.getDisplay().getDisplayId());
-        if (!InputManager.getInstance()
+        if (!mContext.getSystemService(InputManager.class)
                 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
             Log.e(TAG, "Inject input event fail");
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index e7beeeb..3a3a378 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -64,8 +64,8 @@
 
         stopInternal();
 
-        mInputMonitor = InputManager.getInstance().monitorGestureInput(GESTURE_MONITOR,
-                mContext.getDisplayId());
+        mInputMonitor = mContext.getSystemService(InputManager.class)
+                .monitorGestureInput(GESTURE_MONITOR, mContext.getDisplayId());
         InputChannel inputChannel = mInputMonitor.getInputChannel();
 
         BubblesNavBarMotionEventHandler motionEventHandler =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
new file mode 100644
index 0000000..20da54e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+/** Constants that can be used by both Shell and other users of the library, e.g. Launcher */
+public class DragAndDropConstants {
+
+    /**
+     * An Intent extra that Launcher can use to specify a region of the screen where Shell should
+     * ignore drag events.
+     */
+    public static final String EXTRA_DISALLOW_HIT_REGION = "DISALLOW_HIT_REGION";
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index d93a901..df94b41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -34,6 +34,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
@@ -53,6 +54,7 @@
 import android.content.pm.LauncherApps;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -86,6 +88,7 @@
     private final Starter mStarter;
     private final SplitScreenController mSplitScreen;
     private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
+    private final RectF mDisallowHitRegion = new RectF();
 
     private InstanceId mLoggerSessionId;
     private DragSession mSession;
@@ -111,6 +114,12 @@
         mSession = new DragSession(mActivityTaskManager, displayLayout, data);
         // TODO(b/169894807): Also update the session data with task stack changes
         mSession.update();
+        RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
+        if (disallowHitRegion == null) {
+            mDisallowHitRegion.setEmpty();
+        } else {
+            mDisallowHitRegion.set(disallowHitRegion);
+        }
     }
 
     /**
@@ -218,6 +227,9 @@
      */
     @Nullable
     Target getTargetAtLocation(int x, int y) {
+        if (mDisallowHitRegion.contains(x, y)) {
+            return null;
+        }
         for (int i = mTargets.size() - 1; i >= 0; i--) {
             DragAndDropPolicy.Target t = mTargets.get(i);
             if (t.hitRegion.contains(x, y)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 3ade1ed..44fd8ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -118,7 +118,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout());
+        mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
         recomputeDropTargets();
 
         final int orientation = getResources().getConfiguration().orientation;
@@ -369,7 +369,9 @@
 
         // Start animating the drop UI out with the drag surface
         hide(event, dropCompleteCallback);
-        hideDragSurface(dragSurface);
+        if (handledDrop) {
+            hideDragSurface(dragSurface);
+        }
         return handledDrop;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 41ff0b3..fee9140 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -228,7 +228,7 @@
 
         if (mIsEnabled) {
             // Register input event receiver
-            mInputMonitor = InputManager.getInstance().monitorGestureInput(
+            mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
                     "pip-resize", mDisplayId);
             try {
                 mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 19d8cfa..7833cfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1705,9 +1705,7 @@
         }
 
         mSyncQueue.queue(wct);
-        mSyncQueue.runInSync(t -> {
-            setDividerVisibility(mainStageVisible, t);
-        });
+        setDividerVisibility(mainStageVisible, null);
     }
 
     private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
@@ -1789,6 +1787,10 @@
 
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    if (dividerLeash != null && dividerLeash.isValid()) {
+                        transaction.setAlpha(dividerLeash, 1);
+                        transaction.apply();
+                    }
                     mTransactionPool.release(transaction);
                     mDividerFadeInAnimator = 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 c2da705..8e916e6 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
@@ -186,7 +186,7 @@
 
     private boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
         return change.getTaskInfo() != null
-                && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME;
+                && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
     }
 
     private boolean isWallpaper(@NonNull TransitionInfo.Change change) {
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 0826fe2..0a67477 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
@@ -709,15 +709,22 @@
         for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
             final ActiveTransition toMerge = mActiveTransitions.get(iA);
             if (!toMerge.mMerged) break;
-            // aborted transitions have no start/finish transactions
-            if (mActiveTransitions.get(iA).mStartT == null) break;
-            if (fullFinish == null) {
-                fullFinish = new SurfaceControl.Transaction();
-            }
             // Include start. It will be a no-op if it was already applied. Otherwise, we need it
             // to maintain consistent state.
-            fullFinish.merge(mActiveTransitions.get(iA).mStartT);
-            fullFinish.merge(mActiveTransitions.get(iA).mFinishT);
+            if (toMerge.mStartT != null) {
+                if (fullFinish == null) {
+                    fullFinish = toMerge.mStartT;
+                } else {
+                    fullFinish.merge(toMerge.mStartT);
+                }
+            }
+            if (toMerge.mFinishT != null) {
+                if (fullFinish == null) {
+                    fullFinish = toMerge.mFinishT;
+                } else {
+                    fullFinish.merge(toMerge.mFinishT);
+                }
+            }
         }
         if (fullFinish != null) {
             fullFinish.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index de5f2f4..75bc985 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -505,7 +505,7 @@
     }
 
     private void createInputChannel(int displayId) {
-        final InputManager inputManager = InputManager.getInstance();
+        final InputManager inputManager = mContext.getSystemService(InputManager.class);
         final InputMonitor inputMonitor =
                 mInputMonitorFactory.create(inputManager, mContext);
         final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index aea3404..d0fcd86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -65,7 +65,7 @@
                 InputDevice.SOURCE_KEYBOARD);
 
         ev.setDisplayId(mContext.getDisplay().getDisplayId());
-        if (!InputManager.getInstance()
+        if (!mContext.getSystemService(InputManager.class)
                 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
             Log.e(TAG, "Inject input event fail");
         }
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index cbebae9..ecf6cfc 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -464,7 +464,7 @@
         SkBitmap baseBitmap = getSkBitmap();
         SkBitmap gainmapBitmap = gainmap()->bitmap->getSkBitmap();
         SkJpegEncoder::Options options{.fQuality = quality};
-        return SkJpegGainmapEncoder::EncodeJpegR(stream, baseBitmap.pixmap(), options,
+        return SkJpegGainmapEncoder::EncodeHDRGM(stream, baseBitmap.pixmap(), options,
                                                  gainmapBitmap.pixmap(), options, gainmap()->info);
     }
 #endif
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 40a4858..d2b21ae 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -849,7 +849,7 @@
         if (ringtoneUri != null) {
             final Uri cacheUri = getCacheForType(type, context.getUserId());
             try (InputStream in = openRingtone(context, ringtoneUri);
-                    OutputStream out = resolver.openOutputStream(cacheUri)) {
+                    OutputStream out = resolver.openOutputStream(cacheUri, "wt")) {
                 FileUtils.copy(in, out);
             } catch (IOException e) {
                 Log.w(TAG, "Failed to cache ringtone: " + e);
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index 6e2927d..720e46d 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -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 5G experience</string>
+    <string name="performance_boost_notification_title">Improve your app experience</string>
     <!-- Notification detail text for the performance boost notification. -->
-    <string name="performance_boost_notification_detail">%1$s recommends buying a performance boost plan. Tap to buy through %2$s.</string>
+    <string name="performance_boost_notification_detail">Tap to visit %s\'s website and learn more.</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/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index d4ce5f5..23b9766 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -317,7 +317,7 @@
                         .setContentTitle(res.getString(
                                 R.string.performance_boost_notification_title))
                         .setContentText(String.format(res.getString(
-                                R.string.performance_boost_notification_detail), carrier, carrier))
+                                R.string.performance_boost_notification_detail), carrier))
                         .setSmallIcon(R.drawable.ic_performance_boost)
                         .setContentIntent(createContentIntent(context, intent, 1))
                         .setDeleteIntent(intent.getParcelableExtra(
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 95b49a8..73f8730 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -21,7 +21,6 @@
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupManagerMonitor;
-import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.BackupTransport;
 import android.app.backup.RestoreDescription;
@@ -48,6 +47,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 
 /**
@@ -915,6 +915,7 @@
                     Log.i(TAG, "\tdataType: " + result.getDataType());
                     Log.i(TAG, "\tsuccessCount: " + result.getSuccessCount());
                     Log.i(TAG, "\tfailCount: " + result.getFailCount());
+                    Log.i(TAG, "\tmetadataHash: " + Arrays.toString(result.getMetadataHash()));
 
                     if (!result.getErrors().isEmpty()) {
                         Log.i(TAG, "\terrors {");
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 45c4f88..9ee6fbd 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
           package="com.android.packageinstaller">
 
     <original-package android:name="com.android.packageinstaller" />
@@ -149,6 +150,9 @@
                   android:authorities="com.google.android.packageinstaller.wear.provider"
                   android:grantUriPermissions="true"
                   android:exported="false" />
+
+        <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
+            tools:node="remove" />
     </application>
 
 </manifest>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 3b90275..37ae2d4 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -55,6 +55,7 @@
 
     private int mMaxHeight = SIZE_UNSPECIFIED;
     private int mImageResId;
+    private boolean mCacheComposition = true;
     private boolean mIsAutoScale;
     private Uri mImageUri;
     private Drawable mImageDrawable;
@@ -133,6 +134,7 @@
         lp.width = screenWidth < screenHeight ? screenWidth : screenHeight;
         illustrationFrame.setLayoutParams(lp);
 
+        illustrationView.setCacheComposition(mCacheComposition);
         handleImageWithAnimation(illustrationView);
         handleImageFrameMaxHeight(backgroundView, illustrationView);
 
@@ -427,6 +429,8 @@
             TypedArray a = context.obtainStyledAttributes(attrs,
                     R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
             mImageResId = a.getResourceId(R.styleable.LottieAnimationView_lottie_rawRes, 0);
+            mCacheComposition = a.getBoolean(
+                    R.styleable.LottieAnimationView_lottie_cacheComposition, true);
 
             a = context.obtainStyledAttributes(attrs,
                     R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 5a9e780..cc60382 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -14,7 +14,7 @@
   ~ limitations under the License
   -->
 
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <style name="TextAppearanceSmall">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
     </style>
@@ -73,7 +73,7 @@
     </style>
 
     <style name="TextAppearanceBroadcastDialogButton" parent="@android:TextAppearance.DeviceDefault.Headline">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
     </style>
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
index a1d9d90..e42b589 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.animation.LaunchableViewDelegate
 
 /** An [ImageView] that also implements [LaunchableView]. */
-class LaunchableImageView : ImageView, LaunchableView {
+open class LaunchableImageView : ImageView, LaunchableView {
     private val delegate =
         LaunchableViewDelegate(
             this,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
index 286996d..1476695 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.animation.LaunchableViewDelegate
 
 /** A [TextView] that also implements [LaunchableView]. */
-class LaunchableTextView : TextView, LaunchableView {
+open class LaunchableTextView : TextView, LaunchableView {
     private val delegate =
         LaunchableViewDelegate(
             this,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index 442c6fa..bd91c65 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -68,7 +68,7 @@
     private fun applyConfigToShader() {
         with(rippleShader) {
             setCenter(config.centerX, config.centerY)
-            setMaxSize(config.maxWidth, config.maxHeight)
+            rippleSize.setMaxSize(config.maxWidth, config.maxHeight)
             pixelDensity = config.pixelDensity
             color = ColorUtils.setAlphaComponent(config.color, config.opacity)
             sparkleStrength = config.sparkleStrength
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 61ca90a..0b842ad 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -15,9 +15,10 @@
  */
 package com.android.systemui.surfaceeffects.ripple
 
-import android.graphics.PointF
 import android.graphics.RuntimeShader
+import android.util.Log
 import android.util.MathUtils
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
 import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
@@ -44,6 +45,8 @@
     }
     // language=AGSL
     companion object {
+        private val TAG = RippleShader::class.simpleName
+
         // Default fade in/ out values. The value range is [0,1].
         const val DEFAULT_FADE_IN_START = 0f
         const val DEFAULT_FADE_OUT_END = 1f
@@ -99,7 +102,7 @@
             vec4 main(vec2 p) {
                 float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
                     in_thickness), in_blur);
-                float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
+                float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius),
                     in_blur);
                 float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
                     * (1.-sparkleRing) * in_fadeSparkle;
@@ -184,12 +187,17 @@
         setFloatUniform("in_center", x, y)
     }
 
-    /** Max width of the ripple. */
-    private var maxSize: PointF = PointF()
-    fun setMaxSize(width: Float, height: Float) {
-        maxSize.x = width
-        maxSize.y = height
-    }
+    /**
+     * Blur multipliers for the ripple.
+     *
+     * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to
+     * add more blur.
+     */
+    var blurStart: Float = 1.25f
+    var blurEnd: Float = 0.5f
+
+    /** Size of the ripple. */
+    val rippleSize = RippleSize()
 
     /**
      * Linear progress of the ripple. Float value between [0, 1].
@@ -209,15 +217,19 @@
     /** Progress with Standard easing curve applied. */
     private var progress: Float = 0.0f
         set(value) {
-            currentWidth = maxSize.x * value
-            currentHeight = maxSize.y * value
-            setFloatUniform("in_size", currentWidth, currentHeight)
+            field = value
 
-            setFloatUniform("in_thickness", maxSize.y * value * 0.5f)
-            // radius should not exceed width and height values.
-            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value)
+            rippleSize.update(value)
 
-            setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
+            setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight)
+            setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f)
+            // Corner radius is always max of the min between the current width and height.
+            setFloatUniform(
+                "in_cornerRadius",
+                Math.min(rippleSize.currentWidth, rippleSize.currentHeight)
+            )
+
+            setFloatUniform("in_blur", MathUtils.lerp(blurStart, blurEnd, value))
         }
 
     /** Play time since the start of the effect. */
@@ -264,12 +276,6 @@
             setFloatUniform("in_pixelDensity", value)
         }
 
-    var currentWidth: Float = 0f
-        private set
-
-    var currentHeight: Float = 0f
-        private set
-
     /** Parameters that are used to fade in/ out of the sparkle ring. */
     val sparkleRingFadeParams =
         FadeParams(
@@ -342,4 +348,102 @@
         /** The endpoint of the fade out, given that the animation goes from 0 to 1. */
         var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
     )
+
+    /**
+     * Desired size of the ripple at a point t in [progress].
+     *
+     * <p>Note that [progress] is curved and normalized. Below is an example usage:
+     * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height=
+     * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f)
+     *
+     * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into:
+     * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height=
+     * maxHeight)
+     */
+    data class SizeAtProgress(
+        /** Time t in [0,1] progress range. */
+        var t: Float,
+        /** Target width size of the ripple at time [t]. */
+        var width: Float,
+        /** Target height size of the ripple at time [t]. */
+        var height: Float
+    )
+
+    /** Updates and stores the ripple size. */
+    inner class RippleSize {
+        @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>()
+        @VisibleForTesting var currentSizeIndex = 0
+        @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f)
+
+        var currentWidth: Float = 0f
+            private set
+        var currentHeight: Float = 0f
+            private set
+
+        /**
+         * Sets the max size of the ripple.
+         *
+         * <p>Use this if the ripple shape simply changes linearly.
+         */
+        fun setMaxSize(width: Float, height: Float) {
+            setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height))
+        }
+
+        /**
+         * Sets the list of [sizes].
+         *
+         * <p>Note that setting this clears the existing sizes.
+         */
+        fun setSizeAtProgresses(vararg sizes: SizeAtProgress) {
+            // Reset everything.
+            this.sizes.clear()
+            currentSizeIndex = 0
+
+            this.sizes.addAll(sizes)
+            this.sizes.sortBy { it.t }
+        }
+
+        /**
+         * Updates the current ripple size based on the progress.
+         *
+         * <p>Should be called when progress updates.
+         */
+        fun update(progress: Float) {
+            val targetIndex = updateTargetIndex(progress)
+            val prevIndex = Math.max(targetIndex - 1, 0)
+
+            val targetSize = sizes[targetIndex]
+            val prevSize = sizes[prevIndex]
+
+            val subProgress = subProgress(prevSize.t, targetSize.t, progress)
+
+            currentWidth = targetSize.width * subProgress + prevSize.width
+            currentHeight = targetSize.height * subProgress + prevSize.height
+        }
+
+        private fun updateTargetIndex(progress: Float): Int {
+            if (sizes.isEmpty()) {
+                // It could be empty on init.
+                if (progress > 0f) {
+                    Log.e(
+                        TAG,
+                        "Did you forget to set the ripple size? Use [setMaxSize] or " +
+                            "[setSizeAtProgresses] before playing the animation."
+                    )
+                }
+                // If there's no size is set, we set everything to 0 and return early.
+                setSizeAtProgresses(initialSize)
+                return currentSizeIndex
+            }
+
+            var candidate = sizes[currentSizeIndex]
+
+            while (progress > candidate.t) {
+                currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1)
+                candidate = sizes[currentSizeIndex]
+            }
+
+            return currentSizeIndex
+        }
+    }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index 3c9328c..4c7c435 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -45,12 +45,8 @@
 
     var duration: Long = 1750
 
-    private var maxWidth: Float = 0.0f
-    private var maxHeight: Float = 0.0f
     fun setMaxSize(maxWidth: Float, maxHeight: Float) {
-        this.maxWidth = maxWidth
-        this.maxHeight = maxHeight
-        rippleShader.setMaxSize(maxWidth, maxHeight)
+        rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight)
     }
 
     private var centerX: Float = 0.0f
@@ -84,6 +80,106 @@
         ripplePaint.shader = rippleShader
     }
 
+    /**
+     * Sets the fade parameters for the base ring.
+     *
+     * <p>Base ring indicates a blurred ring below the sparkle ring. See
+     * [RippleShader.baseRingFadeParams].
+     */
+    @JvmOverloads
+    fun setBaseRingFadeParams(
+        fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart,
+        fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd,
+        fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart,
+        fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd
+    ) {
+        setFadeParams(
+            rippleShader.baseRingFadeParams,
+            fadeInStart,
+            fadeInEnd,
+            fadeOutStart,
+            fadeOutEnd
+        )
+    }
+
+    /**
+     * Sets the fade parameters for the sparkle ring.
+     *
+     * <p>Sparkle ring refers to the ring that's drawn on top of the base ring. See
+     * [RippleShader.sparkleRingFadeParams].
+     */
+    @JvmOverloads
+    fun setSparkleRingFadeParams(
+        fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart,
+        fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd,
+        fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart,
+        fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd
+    ) {
+        setFadeParams(
+            rippleShader.sparkleRingFadeParams,
+            fadeInStart,
+            fadeInEnd,
+            fadeOutStart,
+            fadeOutEnd
+        )
+    }
+
+    /**
+     * Sets the fade parameters for the center fill.
+     *
+     * <p>One common use case is set all the params to 1, which completely removes the center fill.
+     * See [RippleShader.centerFillFadeParams].
+     */
+    @JvmOverloads
+    fun setCenterFillFadeParams(
+        fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart,
+        fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd,
+        fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart,
+        fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd
+    ) {
+        setFadeParams(
+            rippleShader.centerFillFadeParams,
+            fadeInStart,
+            fadeInEnd,
+            fadeOutStart,
+            fadeOutEnd
+        )
+    }
+
+    private fun setFadeParams(
+        fadeParams: RippleShader.FadeParams,
+        fadeInStart: Float,
+        fadeInEnd: Float,
+        fadeOutStart: Float,
+        fadeOutEnd: Float
+    ) {
+        with(fadeParams) {
+            this.fadeInStart = fadeInStart
+            this.fadeInEnd = fadeInEnd
+            this.fadeOutStart = fadeOutStart
+            this.fadeOutEnd = fadeOutEnd
+        }
+    }
+
+    /**
+     * Sets blur multiplier at start and end of the progress.
+     *
+     * <p>It interpolates between [start] and [end]. No need to set it if using default blur.
+     */
+    fun setBlur(start: Float, end: Float) {
+        rippleShader.blurStart = start
+        rippleShader.blurEnd = end
+    }
+
+    /**
+     * Sets the list of [RippleShader.SizeAtProgress].
+     *
+     * <p>Note that this clears the list before it sets with the new data.
+     */
+    fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) {
+        rippleShader.rippleSize.setSizeAtProgresses(*targetSizes)
+    }
+
     @JvmOverloads
     fun startRipple(onAnimationEnd: Runnable? = null) {
         if (animator.isRunning) {
@@ -133,13 +229,13 @@
         }
         // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
         // active effect area. Values here should be kept in sync with the animation implementation
-        // in the ripple shader. (Twice bigger)
+        // in the ripple shader.
         if (rippleShape == RippleShape.CIRCLE) {
-            val maskRadius = rippleShader.currentWidth
+            val maskRadius = rippleShader.rippleSize.currentWidth
             canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
-        } else {
-            val maskWidth = rippleShader.currentWidth * 2
-            val maskHeight = rippleShader.currentHeight * 2
+        } else if (rippleShape == RippleShape.ELLIPSE) {
+            val maskWidth = rippleShader.rippleSize.currentWidth * 2
+            val maskHeight = rippleShader.rippleSize.currentHeight * 2
             canvas.drawRect(
                 /* left= */ centerX - maskWidth,
                 /* top= */ centerY - maskHeight,
@@ -147,6 +243,10 @@
                 /* bottom= */ centerY + maskHeight,
                 ripplePaint
             )
+        } else { // RippleShape.RoundedBox
+            // No masking for the rounded box, as it has more blur which requires larger bounds.
+            // Masking creates sharp bounds even when the masking is 4 times bigger.
+            canvas.drawPaint(ripplePaint)
         }
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 8b2f466..7889893 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
@@ -50,9 +50,9 @@
 
             float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
                 float borderThickness) {
-                float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
-                float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
-                    cornerRadius - borderThickness);
+                float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness),
+                    cornerRadius + borderThickness);
+                float innerRoundBox = sdRoundedBox(p, size, cornerRadius);
                 return subtract(outerRoundBox, innerRoundBox);
             }
         """
@@ -69,10 +69,13 @@
 
             vec2 u = wh*p, v = wh*wh;
 
-            float U1 = u.y/2.0;  float U5 = 4.0*U1;
-            float U2 = v.y-v.x;  float U6 = 6.0*U1;
-            float U3 = u.x-U2;   float U7 = 3.0*U3;
+            float U1 = u.y/2.0;
+            float U2 = v.y-v.x;
+            float U3 = u.x-U2;
             float U4 = u.x+U2;
+            float U5 = 4.0*U1;
+            float U6 = 6.0*U1;
+            float U7 = 3.0*U3;
 
             float t = 0.5;
             for (int i = 0; i < 3; i ++) {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 1c2f38b..ab4aca5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -113,7 +113,7 @@
     fun onColorPaletteChanged(resources: Resources) {}
 
     /** Call whenever the weather data should update */
-    fun onWeatherDataChanged(data: Weather) {}
+    fun onWeatherDataChanged(data: WeatherData) {}
 }
 
 /** Methods which trigger various clock animations */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
deleted file mode 100644
index 302f175..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.android.systemui.plugins
-
-import android.os.Bundle
-
-class Weather(val conditions: WeatherStateIcon, val temperature: Int, val isCelsius: Boolean) {
-    companion object {
-        private const val TAG = "Weather"
-        private const val WEATHER_STATE_ICON_KEY = "weather_state_icon_extra_key"
-        private const val TEMPERATURE_VALUE_KEY = "temperature_value_extra_key"
-        private const val TEMPERATURE_UNIT_KEY = "temperature_unit_extra_key"
-        private const val INVALID_TEMPERATURE = Int.MIN_VALUE
-
-        fun fromBundle(extras: Bundle): Weather? {
-            val icon =
-                WeatherStateIcon.fromInt(
-                    extras.getInt(WEATHER_STATE_ICON_KEY, WeatherStateIcon.UNKNOWN_ICON.id)
-                )
-            if (icon == null || icon == WeatherStateIcon.UNKNOWN_ICON) {
-                return null
-            }
-            val temperature = extras.getInt(TEMPERATURE_VALUE_KEY, INVALID_TEMPERATURE)
-            if (temperature == INVALID_TEMPERATURE) {
-                return null
-            }
-            return Weather(icon, temperature, extras.getBoolean(TEMPERATURE_UNIT_KEY))
-        }
-    }
-
-    enum class WeatherStateIcon(val id: Int) {
-        UNKNOWN_ICON(0),
-
-        // Clear, day & night.
-        SUNNY(1),
-        CLEAR_NIGHT(2),
-
-        // Mostly clear, day & night.
-        MOSTLY_SUNNY(3),
-        MOSTLY_CLEAR_NIGHT(4),
-
-        // Partly cloudy, day & night.
-        PARTLY_CLOUDY(5),
-        PARTLY_CLOUDY_NIGHT(6),
-
-        // Mostly cloudy, day & night.
-        MOSTLY_CLOUDY_DAY(7),
-        MOSTLY_CLOUDY_NIGHT(8),
-        CLOUDY(9),
-        HAZE_FOG_DUST_SMOKE(10),
-        DRIZZLE(11),
-        HEAVY_RAIN(12),
-        SHOWERS_RAIN(13),
-
-        // Scattered showers, day & night.
-        SCATTERED_SHOWERS_DAY(14),
-        SCATTERED_SHOWERS_NIGHT(15),
-
-        // Isolated scattered thunderstorms, day & night.
-        ISOLATED_SCATTERED_TSTORMS_DAY(16),
-        ISOLATED_SCATTERED_TSTORMS_NIGHT(17),
-        STRONG_TSTORMS(18),
-        BLIZZARD(19),
-        BLOWING_SNOW(20),
-        FLURRIES(21),
-        HEAVY_SNOW(22),
-
-        // Scattered snow showers, day & night.
-        SCATTERED_SNOW_SHOWERS_DAY(23),
-        SCATTERED_SNOW_SHOWERS_NIGHT(24),
-        SNOW_SHOWERS_SNOW(25),
-        MIXED_RAIN_HAIL_RAIN_SLEET(26),
-        SLEET_HAIL(27),
-        TORNADO(28),
-        TROPICAL_STORM_HURRICANE(29),
-        WINDY_BREEZY(30),
-        WINTRY_MIX_RAIN_SNOW(31);
-
-        companion object {
-            fun fromInt(value: Int) = values().firstOrNull { it.id == value }
-        }
-    }
-
-    override fun toString(): String {
-        return "$conditions $temperature${if (isCelsius) "C" else "F"}"
-    }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
new file mode 100644
index 0000000..52dfc55
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -0,0 +1,107 @@
+package com.android.systemui.plugins
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+
+class WeatherData
+private constructor(
+    val description: String,
+    val state: WeatherStateIcon,
+    val useCelsius: Boolean,
+    val temperature: Int,
+) {
+    companion object {
+        private const val TAG = "WeatherData"
+        @VisibleForTesting const val DESCRIPTION_KEY = "description"
+        @VisibleForTesting const val STATE_KEY = "state"
+        @VisibleForTesting const val USE_CELSIUS_KEY = "use_celsius"
+        @VisibleForTesting const val TEMPERATURE_KEY = "temperature"
+        private const val INVALID_WEATHER_ICON_STATE = -1
+
+        fun fromBundle(extras: Bundle): WeatherData? {
+            val description = extras.getString(DESCRIPTION_KEY)
+            val state =
+                WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE))
+            val temperature = readIntFromBundle(extras, TEMPERATURE_KEY)
+            return if (
+                description == null ||
+                    state == null ||
+                    !extras.containsKey(USE_CELSIUS_KEY) ||
+                    temperature == null
+            )
+                null
+            else
+                WeatherData(
+                    description = description,
+                    state = state,
+                    useCelsius = extras.getBoolean(USE_CELSIUS_KEY),
+                    temperature = temperature
+                )
+        }
+
+        private fun readIntFromBundle(extras: Bundle, key: String): Int? =
+            try {
+                extras.getString(key).toInt()
+            } catch (e: Exception) {
+                null
+            }
+    }
+
+    enum class WeatherStateIcon(val id: Int) {
+        UNKNOWN_ICON(0),
+
+        // Clear, day & night.
+        SUNNY(1),
+        CLEAR_NIGHT(2),
+
+        // Mostly clear, day & night.
+        MOSTLY_SUNNY(3),
+        MOSTLY_CLEAR_NIGHT(4),
+
+        // Partly cloudy, day & night.
+        PARTLY_CLOUDY(5),
+        PARTLY_CLOUDY_NIGHT(6),
+
+        // Mostly cloudy, day & night.
+        MOSTLY_CLOUDY_DAY(7),
+        MOSTLY_CLOUDY_NIGHT(8),
+        CLOUDY(9),
+        HAZE_FOG_DUST_SMOKE(10),
+        DRIZZLE(11),
+        HEAVY_RAIN(12),
+        SHOWERS_RAIN(13),
+
+        // Scattered showers, day & night.
+        SCATTERED_SHOWERS_DAY(14),
+        SCATTERED_SHOWERS_NIGHT(15),
+
+        // Isolated scattered thunderstorms, day & night.
+        ISOLATED_SCATTERED_TSTORMS_DAY(16),
+        ISOLATED_SCATTERED_TSTORMS_NIGHT(17),
+        STRONG_TSTORMS(18),
+        BLIZZARD(19),
+        BLOWING_SNOW(20),
+        FLURRIES(21),
+        HEAVY_SNOW(22),
+
+        // Scattered snow showers, day & night.
+        SCATTERED_SNOW_SHOWERS_DAY(23),
+        SCATTERED_SNOW_SHOWERS_NIGHT(24),
+        SNOW_SHOWERS_SNOW(25),
+        MIXED_RAIN_HAIL_RAIN_SLEET(26),
+        SLEET_HAIL(27),
+        TORNADO(28),
+        TROPICAL_STORM_HURRICANE(29),
+        WINDY_BREEZY(30),
+        WINTRY_MIX_RAIN_SNOW(31);
+
+        companion object {
+            fun fromInt(value: Int) = values().firstOrNull { it.id == value }
+        }
+    }
+
+    override fun toString(): String {
+        val unit = if (useCelsius) "C" else "F"
+        return "$state (\"$description\") $temperature°$unit"
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index e1e8063..90f44a7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -36,7 +36,6 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
-import android.app.TaskInfo;
 import android.app.WindowConfiguration;
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -48,7 +47,7 @@
 import android.window.TransitionInfo.Change;
 
 import java.util.ArrayList;
-import java.util.function.BiPredicate;
+import java.util.function.Predicate;
 
 /**
  * Some utility methods for creating {@link RemoteAnimationTarget} instances.
@@ -145,6 +144,18 @@
     public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
             TransitionInfo info, SurfaceControl.Transaction t,
             @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+        final SurfaceControl leash = createLeash(info, change, order, t);
+        if (leashMap != null) {
+            leashMap.put(change.getLeash(), leash);
+        }
+        return newTarget(change, order, leash);
+    }
+
+    /**
+     * Creates a new RemoteAnimationTarget from the provided change and leash
+     */
+    public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
+            SurfaceControl leash) {
         int taskId;
         boolean isNotInRecents;
         ActivityManager.RunningTaskInfo taskInfo;
@@ -169,7 +180,7 @@
                 newModeToLegacyMode(change.getMode()),
                 // TODO: once we can properly sync transactions across process,
                 // then get rid of this leash.
-                createLeash(info, change, order, t),
+                leash,
                 (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0,
                 null,
                 // TODO(shell-transitions): we need to send content insets? evaluate how its used.
@@ -190,9 +201,6 @@
         target.setWillShowImeOnTarget(
                 (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0);
         target.setRotationChange(change.getEndRotation() - change.getStartRotation());
-        if (leashMap != null) {
-            leashMap.put(change.getLeash(), target.leash);
-        }
         return target;
     }
 
@@ -204,18 +212,7 @@
      */
     public static RemoteAnimationTarget[] wrapApps(TransitionInfo info,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
-        SparseBooleanArray childTaskTargets = new SparseBooleanArray();
-        return wrap(info, t, leashMap, (change, taskInfo) -> {
-            // Children always come before parent since changes are in top-to-bottom z-order.
-            if ((taskInfo == null) || childTaskTargets.get(taskInfo.taskId)) {
-                // has children, so not a leaf. Skip.
-                return false;
-            }
-            if (taskInfo.hasParentTask()) {
-                childTaskTargets.put(taskInfo.parentTaskId, true);
-            }
-            return true;
-        });
+        return wrap(info, t, leashMap, new LeafTaskFilter());
     }
 
     /**
@@ -228,21 +225,56 @@
      */
     public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
-        return wrap(info, t, leashMap, (change, taskInfo) -> (taskInfo == null)
-                && wallpapers == change.hasFlags(FLAG_IS_WALLPAPER)
-                && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
+        return wrap(info, t, leashMap, (change) ->
+                (wallpapers ? isWallpaper(change) : isNonApp(change)));
     }
 
     private static RemoteAnimationTarget[] wrap(TransitionInfo info,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
-            BiPredicate<Change, TaskInfo> filter) {
+            Predicate<Change> filter) {
         final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
         for (int i = 0; i < info.getChanges().size(); i++) {
             TransitionInfo.Change change = info.getChanges().get(i);
-            if (filter.test(change, change.getTaskInfo())) {
+            if (filter.test(change)) {
                 out.add(newTarget(change, info.getChanges().size() - i, info, t, leashMap));
             }
         }
         return out.toArray(new RemoteAnimationTarget[out.size()]);
     }
+
+    /** Returns `true` if `change` is a wallpaper. */
+    public static boolean isWallpaper(Change change) {
+        return (change.getTaskInfo() == null)
+                && change.hasFlags(FLAG_IS_WALLPAPER)
+                && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+    }
+
+    /** Returns `true` if `change` is not an app window or wallpaper. */
+    public static boolean isNonApp(Change change) {
+        return (change.getTaskInfo() == null)
+                && !change.hasFlags(FLAG_IS_WALLPAPER)
+                && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+    }
+
+    /**
+     * Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
+     * MUST call `test` in the same order that the changes appear in the TransitionInfo.
+     */
+    public static class LeafTaskFilter implements Predicate<Change> {
+        private final SparseBooleanArray mChildTaskTargets = new SparseBooleanArray();
+
+        @Override
+        public boolean test(Change change) {
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            // Children always come before parent since changes are in top-to-bottom z-order.
+            if ((taskInfo == null) || mChildTaskTargets.get(taskInfo.taskId)) {
+                // has children, so not a leaf. Skip.
+                return false;
+            }
+            if (taskInfo.hasParentTask()) {
+                mChildTaskTargets.put(taskInfo.parentTaskId, true);
+            }
+            return true;
+        }
+    }
 }
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 44c0e16..2a37bd3 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
@@ -18,7 +18,6 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
@@ -72,71 +71,23 @@
             public void startAnimation(IBinder transition, TransitionInfo info,
                     SurfaceControl.Transaction t,
                     IRemoteTransitionFinishedCallback finishedCallback) {
-                final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
-                final RemoteAnimationTarget[] apps =
-                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
-                final RemoteAnimationTarget[] wallpapers =
-                        RemoteAnimationTargetCompat.wrapNonApps(
-                                info, true /* wallpapers */, t, leashMap);
                 // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
                 mToken = transition;
-                // This transition is for opening recents, so recents is on-top. We want to draw
-                // the current going-away tasks on top of recents, though, so move them to front.
-                // Note that we divide up the "layer space" into 3 regions each the size of
-                // the change count. This way we can easily move changes into above/below/between
-                // while maintaining their relative ordering.
-                final ArrayList<WindowContainerToken> pausingTasks = new ArrayList<>();
-                WindowContainerToken pipTask = null;
-                WindowContainerToken recentsTask = null;
-                int recentsTaskId = -1;
-                for (int i = apps.length - 1; i >= 0; --i) {
-                    final ActivityManager.RunningTaskInfo taskInfo = apps[i].taskInfo;
-                    if (apps[i].mode == MODE_CLOSING) {
-                        t.setLayer(apps[i].leash, info.getChanges().size() * 3 - i);
-                        if (taskInfo == null) {
-                            continue;
-                        }
-                        // Add to front since we are iterating backwards.
-                        pausingTasks.add(0, taskInfo.token);
-                        if (taskInfo.pictureInPictureParams != null
-                                && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
-                            pipTask = taskInfo.token;
-                        }
-                    } else if (taskInfo != null
-                            && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
-                        // This task is for recents, keep it on top.
-                        t.setLayer(apps[i].leash, info.getChanges().size() * 3 - i);
-                        recentsTask = taskInfo.token;
-                        recentsTaskId = taskInfo.taskId;
-                    } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                        recentsTask = taskInfo.token;
-                        recentsTaskId = taskInfo.taskId;
-                    }
-                }
-                // Also make all the wallpapers opaque since we want the visible from the start
-                for (int i = wallpapers.length - 1; i >= 0; --i) {
-                    t.setAlpha(wallpapers[i].leash, 1);
-                }
-                t.apply();
-                mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask,
-                        recentsTask, recentsTaskId, leashMap, mToken,
-                        (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0);
-                recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
-                        new Rect());
+                mRecentsSession.start(controller, recents, mToken, info, t, finishedCallback);
             }
 
             @Override
             public void mergeAnimation(IBinder transition, TransitionInfo info,
                     SurfaceControl.Transaction t, IBinder mergeTarget,
                     IRemoteTransitionFinishedCallback finishedCallback) {
-                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t)) {
                     try {
                         finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Error merging transition.", e);
                     }
                     // commit taskAppeared after merge transition finished.
-                    mRecentsSession.commitTasksAppearedIfNeeded(recents);
+                    mRecentsSession.commitTasksAppearedIfNeeded();
                 } else {
                     t.close();
                     info.releaseAllSurfaces();
@@ -152,15 +103,16 @@
      */
     @VisibleForTesting
     static class RecentsControllerWrap extends RecentsAnimationControllerCompat {
+        private RecentsAnimationListener mListener = null;
         private RecentsAnimationControllerCompat mWrapped = null;
         private IRemoteTransitionFinishedCallback mFinishCB = null;
-        private ArrayList<WindowContainerToken> mPausingTasks = null;
+        private ArrayList<TaskState> mPausingTasks = null;
         private WindowContainerToken mPipTask = null;
         private WindowContainerToken mRecentsTask = null;
         private int mRecentsTaskId = 0;
         private TransitionInfo mInfo = null;
         private ArrayList<SurfaceControl> mOpeningLeashes = null;
-        private boolean mOpeningHome = false;
+        private boolean mOpeningSeparateHome = false;
         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
         private PictureInPictureSurfaceTransaction mPipTransaction = null;
         private IBinder mTransition = null;
@@ -168,34 +120,79 @@
         private RemoteAnimationTarget[] mAppearedTargets;
         private boolean mWillFinishToHome = false;
 
-        void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
-                IRemoteTransitionFinishedCallback finishCB,
-                ArrayList<WindowContainerToken> pausingTasks, WindowContainerToken pipTask,
-                WindowContainerToken recentsTask, int recentsTaskId, ArrayMap<SurfaceControl,
-                SurfaceControl> leashMap, IBinder transition, boolean keyguardLocked) {
+        void start(RecentsAnimationControllerCompat wrapped, RecentsAnimationListener listener,
+                IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
+                IRemoteTransitionFinishedCallback finishedCallback) {
             if (mInfo != null) {
                 throw new IllegalStateException("Trying to run a new recents animation while"
                         + " recents is already active.");
             }
+            mListener = listener;
             mWrapped = wrapped;
             mInfo = info;
-            mFinishCB = finishCB;
-            mPausingTasks = pausingTasks;
-            mPipTask = pipTask;
-            mRecentsTask = recentsTask;
-            mRecentsTaskId = recentsTaskId;
-            mLeashMap = leashMap;
+            mFinishCB = finishedCallback;
+            mPausingTasks = new ArrayList<>();
+            mPipTask = null;
+            mRecentsTask = null;
+            mRecentsTaskId = -1;
+            mLeashMap = new ArrayMap<>();
             mTransition = transition;
-            mKeyguardLocked = keyguardLocked;
+            mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+
+            final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+            final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
+            RemoteAnimationTargetCompat.LeafTaskFilter leafTaskFilter =
+                    new RemoteAnimationTargetCompat.LeafTaskFilter();
+            // About layering: we divide up the "layer space" into 3 regions (each the size of
+            // the change count). This lets us categorize things into above/below/between
+            // while maintaining their relative ordering.
+            for (int i = 0; i < info.getChanges().size(); ++i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                if (RemoteAnimationTargetCompat.isWallpaper(change)) {
+                    final RemoteAnimationTarget target = newTarget(change,
+                            // wallpapers go into the "below" layer space
+                            info.getChanges().size() - i, info, t, mLeashMap);
+                    wallpapers.add(target);
+                    // Make all the wallpapers opaque since we want them visible from the start
+                    t.setAlpha(target.leash, 1);
+                } else if (leafTaskFilter.test(change)) {
+                    // start by putting everything into the "below" layer space.
+                    final RemoteAnimationTarget target = newTarget(change,
+                            info.getChanges().size() - i, info, t, mLeashMap);
+                    apps.add(target);
+                    if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) {
+                        // raise closing (pausing) task to "above" layer so it isn't covered
+                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+                        mPausingTasks.add(new TaskState(change, target.leash));
+                        if (taskInfo.pictureInPictureParams != null
+                                && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
+                            mPipTask = taskInfo.token;
+                        }
+                    } else if (taskInfo != null
+                            && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
+                        // There's a 3p launcher, so make sure recents goes above that.
+                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+                        mRecentsTask = taskInfo.token;
+                        mRecentsTaskId = taskInfo.taskId;
+                    } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                        mRecentsTask = taskInfo.token;
+                        mRecentsTaskId = taskInfo.taskId;
+                    }
+                }
+            }
+            t.apply();
+            mListener.onAnimationStart(this, apps.toArray(new RemoteAnimationTarget[apps.size()]),
+                    wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
+                    new Rect(0, 0, 0, 0), new Rect());
         }
 
         @SuppressLint("NewApi")
-        boolean merge(TransitionInfo info, SurfaceControl.Transaction t,
-                RecentsAnimationListener recents) {
+        boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
             SparseArray<TransitionInfo.Change> openingTasks = null;
             mAppearedTargets = null;
-            boolean cancelRecents = false;
-            boolean homeGoingAway = false;
+            boolean foundHomeOpening = false;
+            boolean foundRecentsClosing = false;
             boolean hasChangingApp = false;
             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
@@ -203,8 +200,8 @@
                     final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
                     if (taskInfo != null) {
                         if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                            // canceling recents animation
-                            cancelRecents = true;
+                            // This is usually a 3p launcher
+                            foundHomeOpening = true;
                         }
                         if (openingTasks == null) {
                             openingTasks = new SparseArray<>();
@@ -219,19 +216,18 @@
                 } else if (change.getMode() == TRANSIT_CLOSE
                         || change.getMode() == TRANSIT_TO_BACK) {
                     if (mRecentsTask.equals(change.getContainer())) {
-                        homeGoingAway = true;
+                        foundRecentsClosing = true;
                     }
                 } else if (change.getMode() == TRANSIT_CHANGE) {
                     hasChangingApp = true;
                 }
             }
-            if (hasChangingApp && homeGoingAway) {
+            if (hasChangingApp && foundRecentsClosing) {
                 // This happens when a visible app is expanding (usually PiP). In this case,
-                // The transition probably has a special-purpose animation, so finish recents
+                // that transition probably has a special-purpose animation, so finish recents
                 // now and let it do its animation (since recents is going to be occluded).
-                if (!recents.onSwitchToScreenshot(() -> {
-                    finish(true /* toHome */, false /* userLeaveHint */);
-                })) {
+                if (!mListener.onSwitchToScreenshot(
+                        () -> finish(true /* toHome */, false /* userLeaveHint */))) {
                     Log.w(TAG, "Recents callback doesn't support support switching to screenshot"
                             + ", there might be a flicker.");
                     finish(true /* toHome */, false /* userLeaveHint */);
@@ -240,9 +236,9 @@
             }
             if (openingTasks == null) return false;
             int pauseMatches = 0;
-            if (!cancelRecents) {
+            if (!foundHomeOpening) {
                 for (int i = 0; i < openingTasks.size(); ++i) {
-                    if (mPausingTasks.contains(openingTasks.valueAt(i).getContainer())) {
+                    if (TaskState.indexOf(mPausingTasks, openingTasks.valueAt(i)) >= 0) {
                         ++pauseMatches;
                     }
                 }
@@ -262,7 +258,7 @@
             }
             final int layer = mInfo.getChanges().size() * 3;
             mOpeningLeashes = new ArrayList<>();
-            mOpeningHome = cancelRecents;
+            mOpeningSeparateHome = foundHomeOpening;
             final RemoteAnimationTarget[] targets =
                     new RemoteAnimationTarget[openingTasks.size()];
             for (int i = 0; i < openingTasks.size(); ++i) {
@@ -280,9 +276,9 @@
             return true;
         }
 
-        private void commitTasksAppearedIfNeeded(RecentsAnimationListener recents) {
+        private void commitTasksAppearedIfNeeded() {
             if (mAppearedTargets != null) {
-                recents.onTasksAppeared(mAppearedTargets);
+                mListener.onTasksAppeared(mAppearedTargets);
                 mAppearedTargets = null;
             }
         }
@@ -346,27 +342,26 @@
                 // re-showing it's task).
                 for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
                     // reverse order so that index 0 ends up on top
-                    wct.reorder(mPausingTasks.get(i), true /* onTop */);
-                    t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
+                    wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
+                    t.show(mPausingTasks.get(i).mTaskSurface);
                 }
                 if (!mKeyguardLocked && mRecentsTask != null) {
                     wct.restoreTransientOrder(mRecentsTask);
                 }
-            } else if (toHome && mOpeningHome && mPausingTasks != null) {
+            } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
                 // Special situaition where 3p launcher was changed during recents (this happens
                 // during tapltests...). Here we get both "return to home" AND "home opening".
                 // This is basically going home, but we have to restore recents order and also
                 // treat the home "pausing" task properly.
                 for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
-                    final TransitionInfo.Change change = mInfo.getChange(mPausingTasks.get(i));
-                    final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                    if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                    final TaskState state = mPausingTasks.get(i);
+                    if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                         // Treat as opening (see above)
-                        wct.reorder(mPausingTasks.get(i), true /* onTop */);
-                        t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
+                        wct.reorder(state.mToken, true /* onTop */);
+                        t.show(state.mTaskSurface);
                     } else {
                         // Treat as hiding (see below)
-                        t.hide(mInfo.getChange(mPausingTasks.get(i)).getLeash());
+                        t.hide(state.mTaskSurface);
                     }
                 }
                 if (!mKeyguardLocked && mRecentsTask != null) {
@@ -377,13 +372,13 @@
                     if (!sendUserLeaveHint) {
                         // This means recents is not *actually* finishing, so of course we gotta
                         // do special stuff in WMCore to accommodate.
-                        wct.setDoNotPip(mPausingTasks.get(i));
+                        wct.setDoNotPip(mPausingTasks.get(i).mToken);
                     }
                     // Since we will reparent out of the leashes, pre-emptively hide the child
                     // surface to match the leash. Otherwise, there will be a flicker before the
                     // visibility gets committed in Core when using split-screen (in splitscreen,
                     // the leaf-tasks are not "independent" so aren't hidden by normal setup).
-                    t.hide(mInfo.getChange(mPausingTasks.get(i)).getLeash());
+                    t.hide(mPausingTasks.get(i).mTaskSurface);
                 }
                 if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
                     t.show(mInfo.getChange(mPipTask).getLeash());
@@ -404,11 +399,13 @@
             mInfo.releaseAllSurfaces();
             // Reset all members.
             mWrapped = null;
+            mListener = null;
             mFinishCB = null;
             mPausingTasks = null;
+            mAppearedTargets = null;
             mInfo = null;
             mOpeningLeashes = null;
-            mOpeningHome = false;
+            mOpeningSeparateHome = false;
             mLeashMap = null;
             mTransition = null;
         }
@@ -450,4 +447,36 @@
         @Override public void animateNavigationBarToApp(long duration) {
         }
     }
+
+    /** Utility class to track the state of a task as-seen by recents. */
+    private static class TaskState {
+        WindowContainerToken mToken;
+        ActivityManager.RunningTaskInfo mTaskInfo;
+
+        /** The surface/leash of the task provided by Core. */
+        SurfaceControl mTaskSurface;
+
+        /** The (local) animation-leash created for this task. */
+        SurfaceControl mLeash;
+
+        TaskState(TransitionInfo.Change change, SurfaceControl leash) {
+            mToken = change.getContainer();
+            mTaskInfo = change.getTaskInfo();
+            mTaskSurface = change.getLeash();
+            mLeash = leash;
+        }
+
+        static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
+            for (int i = list.size() - 1; i >= 0; --i) {
+                if (list.get(i).mToken.equals(change.getContainer())) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        public String toString() {
+            return "" + mToken + " : " + mLeash;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 1254e1e..92ee373 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -48,7 +48,7 @@
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.shared.regionsampling.RegionSampler
-import com.android.systemui.plugins.Weather
+import com.android.systemui.plugins.WeatherData
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -291,10 +291,8 @@
                 clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
             }
 
-            override fun onWeatherDataChanged(data: Weather?) {
-                if (data != null) {
-                    clock?.events?.onWeatherDataChanged(data)
-                }
+            override fun onWeatherDataChanged(data: WeatherData) {
+                clock?.events?.onWeatherDataChanged(data)
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index eec788b..f164e7d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -492,12 +492,14 @@
             case MotionEvent.ACTION_MOVE:
                 mVelocityTracker.addMovement(event);
                 int pointerIndex = event.findPointerIndex(mActivePointerId);
-                float y = event.getY(pointerIndex);
-                if (mLastTouchY != -1) {
-                    float dy = y - mLastTouchY;
-                    setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER);
+                if (pointerIndex != -1) {
+                    float y = event.getY(pointerIndex);
+                    if (mLastTouchY != -1) {
+                        float dy = y - mLastTouchY;
+                        setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER);
+                    }
+                    mLastTouchY = y;
                 }
-                mLastTouchY = y;
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 23f887e..f1abdc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -17,7 +17,6 @@
 package com.android.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
@@ -36,7 +35,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricSourceType;
 import android.media.AudioManager;
 import android.metrics.LogMaker;
 import android.os.SystemClock;
@@ -321,7 +319,6 @@
                     KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
                 }
             };
-    private boolean mBouncerVisible = false;
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
@@ -356,19 +353,6 @@
                 public void onDevicePolicyManagerStateChanged() {
                     showPrimarySecurityScreen(false);
                 }
-
-                @Override
-                public void onBiometricRunningStateChanged(boolean running,
-                        BiometricSourceType biometricSourceType) {
-                    if (biometricSourceType == FINGERPRINT) {
-                        updateSideFpsVisibility();
-                    }
-                }
-
-                @Override
-                public void onStrongAuthStateChanged(int userId) {
-                    updateSideFpsVisibility();
-                }
             };
 
     @Inject
@@ -460,35 +444,24 @@
             getCurrentSecurityController().onPause();
         }
         mView.onPause();
-        // It might happen that onStartingToHide is not called when the device is locked while on
-        // bouncer.
-        setBouncerVisible(false);
         mView.clearFocus();
     }
 
-    private void updateSideFpsVisibility() {
+    /**
+     * Shows and hides the side finger print sensor animation.
+     *
+     * @param isVisible sets whether we show or hide the side fps animation
+     */
+    public void updateSideFpsVisibility(boolean isVisible) {
         if (!mSideFpsController.isPresent()) {
             return;
         }
-        final boolean sfpsEnabled = getResources().getBoolean(
-                R.bool.config_show_sidefps_hint_on_bouncer);
-        final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
-        final boolean isUnlockingWithFpAllowed =
-                mUpdateMonitor.isUnlockingWithFingerprintAllowed();
 
-        boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning
-                && isUnlockingWithFpAllowed;
-
-        if (DEBUG) {
-            Log.d(TAG, "sideFpsToShow=" + toShow + ", "
-                    + "mBouncerVisible=" + mBouncerVisible + ", "
-                    + "configEnabled=" + sfpsEnabled + ", "
-                    + "fpsDetectionRunning=" + fpsDetectionRunning + ", "
-                    + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed);
-        }
-        if (toShow) {
-            mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                    BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
+        if (isVisible) {
+            mSideFpsController.get().show(
+                    SideFpsUiRequestSource.PRIMARY_BOUNCER,
+                    BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+            );
         } else {
             mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
         }
@@ -637,7 +610,6 @@
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state);
 
             getCurrentSecurityController().onResume(reason);
-            updateSideFpsVisibility();
         }
         mView.onResume(
                 mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
@@ -691,22 +663,15 @@
         if (mCurrentSecurityMode != SecurityMode.None) {
             getCurrentSecurityController().onStartingToHide();
         }
-        setBouncerVisible(false);
     }
 
     /** Called when the bouncer changes visibility. */
-    public void onBouncerVisibilityChanged(@View.Visibility int visibility) {
-        setBouncerVisible(visibility == View.VISIBLE);
-        if (visibility == View.INVISIBLE) {
+    public void onBouncerVisibilityChanged(boolean isVisible) {
+        if (!isVisible) {
             mView.resetScale();
         }
     }
 
-    private void setBouncerVisible(boolean visible) {
-        mBouncerVisible = visible;
-        updateSideFpsVisibility();
-    }
-
     /**
      * Shows the next security screen if there is one.
      * @param authenticated true if the user entered the correct authentication
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 17ce0b9..b8e1499 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -151,7 +151,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
 import com.android.systemui.log.SessionTracker;
-import com.android.systemui.plugins.Weather;
+import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -3303,12 +3303,12 @@
     /**
      * @param data the weather data (temp, conditions, unit) for weather clock to use
      */
-    public void sendWeatherData(Weather data) {
+    public void sendWeatherData(WeatherData data) {
         mHandler.post(()-> {
             handleWeatherDataUpdate(data); });
     }
 
-    private void handleWeatherDataUpdate(Weather data) {
+    private void handleWeatherDataUpdate(WeatherData data) {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 0da799e..38f3e50 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -23,7 +23,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.plugins.Weather;
+import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 
 import java.util.TimeZone;
@@ -61,7 +61,7 @@
     /**
      * Called when receive new weather data.
      */
-    public void onWeatherDataChanged(Weather data) { }
+    public void onWeatherDataChanged(WeatherData data) { }
 
     /**
      * Called when the carrier PLMN or SPN changes.
diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
index 0f00a04..603471b 100644
--- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
@@ -73,6 +73,10 @@
     @BinderThread
     fun onScreenTurnedOn() {
         foldAodAnimationController?.onScreenTurnedOn()
+    }
+
+    @BinderThread
+    fun onScreenTurnedOff() {
         pendingTasks.reset()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 00f5ac2..12b5705 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -58,13 +58,13 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.util.Assert;
 
+import dagger.Lazy;
+
 import java.util.Locale;
 import java.util.Optional;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Class to register system actions with accessibility framework.
  */
@@ -473,7 +473,7 @@
         KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0,
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
                 InputDevice.SOURCE_KEYBOARD, null);
-        InputManager.getInstance()
+        mContext.getSystemService(InputManager.class)
                 .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
         event.recycle();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3e5d16a..e42f051 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -878,7 +878,7 @@
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
                 windowFlags,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 58b230f..4b32759 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -75,7 +75,7 @@
         }
     private var radius: Float = 0f
         set(value) {
-            rippleShader.setMaxSize(value * 2f, value * 2f)
+            rippleShader.rippleSize.setMaxSize(value * 2f, value * 2f)
             field = value
         }
     private var origin: Point = Point()
@@ -364,7 +364,7 @@
 
         if (drawRipple) {
             canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
-                    rippleShader.currentWidth, ripplePaint)
+                    rippleShader.rippleSize.currentWidth, ripplePaint)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 36103f8..cef415c 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,7 +33,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader;
 import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
 import com.android.systemui.surfaceeffects.ripple.RippleView;
 
@@ -44,10 +46,12 @@
  */
 final class WirelessChargingLayout extends FrameLayout {
     private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
-    private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750;
+    private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 3000;
     private static final int SCRIM_COLOR = 0x4C000000;
     private static final int SCRIM_FADE_DURATION = 300;
     private RippleView mRippleView;
+    // This is only relevant to the rounded box ripple.
+    private RippleShader.SizeAtProgress[] mSizeAtProgressArray;
 
     WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
             boolean isDozing, RippleShape rippleShape) {
@@ -125,20 +129,23 @@
         AnimatorSet animatorSet = new AnimatorSet();
         animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator);
 
-        ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
-                "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
-        scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
-        scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR);
-        ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this,
-                "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
-        scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
-        scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
-        scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
-                ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
-                - SCRIM_FADE_DURATION);
-        AnimatorSet animatorSetScrim = new AnimatorSet();
-        animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
-        animatorSetScrim.start();
+        // For tablet docking animation, we don't play the background scrim.
+        if (!Utilities.isTablet(context)) {
+            ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this,
+                    "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR);
+            scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION);
+            scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR);
+            ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this,
+                    "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
+            scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
+            scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
+            scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
+                    ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
+                    - SCRIM_FADE_DURATION);
+            AnimatorSet animatorSetScrim = new AnimatorSet();
+            animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
+            animatorSetScrim.start();
+        }
 
         mRippleView = findViewById(R.id.wireless_charging_ripple);
         mRippleView.setupShader(rippleShape);
@@ -147,7 +154,26 @@
         if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
             mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
             mRippleView.setSparkleStrength(0.22f);
-            mRippleView.setColor(color, 28);
+            mRippleView.setColor(color, 102); // 40% of opacity.
+            mRippleView.setBaseRingFadeParams(
+                    /* fadeInStart = */ 0f,
+                    /* fadeInEnd = */ 0f,
+                    /* fadeOutStart = */ 0.2f,
+                    /* fadeOutEnd= */ 0.47f
+            );
+            mRippleView.setSparkleRingFadeParams(
+                    /* fadeInStart = */ 0f,
+                    /* fadeInEnd = */ 0f,
+                    /* fadeOutStart = */ 0.2f,
+                    /* fadeOutEnd= */ 1f
+            );
+            mRippleView.setCenterFillFadeParams(
+                    /* fadeInStart = */ 0f,
+                    /* fadeInEnd = */ 0f,
+                    /* fadeOutStart = */ 0f,
+                    /* fadeOutEnd= */ 0.2f
+            );
+            mRippleView.setBlur(6.5f, 2.5f);
         } else {
             mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
             mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA);
@@ -246,9 +272,7 @@
             int height = getMeasuredHeight();
             mRippleView.setCenter(width * 0.5f, height * 0.5f);
             if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
-                // Those magic numbers are introduced for visual polish. This aspect ratio maps with
-                // the tablet's docking station.
-                mRippleView.setMaxSize(width * 1.36f, height * 1.46f);
+                updateRippleSizeAtProgressList(width, height);
             } else {
                 float maxSize = Math.max(width, height);
                 mRippleView.setMaxSize(maxSize, maxSize);
@@ -257,4 +281,36 @@
 
         super.onLayout(changed, left, top, right, bottom);
     }
+
+    private void updateRippleSizeAtProgressList(float width, float height) {
+        if (mSizeAtProgressArray == null) {
+            float maxSize = Math.max(width, height);
+            mSizeAtProgressArray = new RippleShader.SizeAtProgress[] {
+                    // Those magic numbers are introduced for visual polish. It starts from a pill
+                    // shape and expand to a full circle.
+                    new RippleShader.SizeAtProgress(0f, 0f, 0f),
+                    new RippleShader.SizeAtProgress(0.3f, width * 0.4f, height * 0.4f),
+                    new RippleShader.SizeAtProgress(1f, maxSize, maxSize)
+            };
+        } else {
+            // Same multipliers, just need to recompute with the new width and height.
+            RippleShader.SizeAtProgress first = mSizeAtProgressArray[0];
+            first.setT(0f);
+            first.setWidth(0f);
+            first.setHeight(0f);
+
+            RippleShader.SizeAtProgress second = mSizeAtProgressArray[1];
+            second.setT(0.3f);
+            second.setWidth(width * 0.4f);
+            second.setHeight(height * 0.4f);
+
+            float maxSize = Math.max(width, height);
+            RippleShader.SizeAtProgress last = mSizeAtProgressArray[2];
+            last.setT(1f);
+            last.setWidth(maxSize);
+            last.setHeight(maxSize);
+        }
+
+        mRippleView.setSizeAtProgresses(mSizeAtProgressArray);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
new file mode 100644
index 0000000..1390b4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+import android.content.ComponentName
+
+/** Returns true if the admin of [userId] disallows keyguard shortcuts. */
+fun DevicePolicyManager.areKeyguardShortcutsDisabled(
+    admin: ComponentName? = null,
+    userId: Int
+): Boolean {
+    val flags = getKeyguardDisabledFeatures(admin, userId)
+    return flags and KEYGUARD_DISABLE_SHORTCUTS_ALL == KEYGUARD_DISABLE_SHORTCUTS_ALL ||
+        flags and KEYGUARD_DISABLE_FEATURES_ALL == KEYGUARD_DISABLE_FEATURES_ALL
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 3a37c6f..f598c36 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -30,7 +30,7 @@
 import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
 import com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
-import com.android.systemui.process.condition.UserProcessCondition;
+import com.android.systemui.process.condition.SystemProcessCondition;
 import com.android.systemui.shared.condition.Condition;
 import com.android.systemui.shared.condition.Monitor;
 
@@ -129,7 +129,7 @@
     @Binds
     @IntoSet
     @Named(DREAM_PRETEXT_CONDITIONS)
-    Condition bindsUserProcessCondition(UserProcessCondition condition);
+    Condition bindSystemProcessCondition(SystemProcessCondition condition);
 
     /** */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index fa4caaf..80f5a18 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -630,7 +630,8 @@
 
     // 2600 - keyboard
     // TODO(b/259352579): Tracking Bug
-    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
+    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT =
+            unreleasedFlag(2600, "shortcut_list_search_layout", teamfood = true)
 
     // TODO(b/259428678): Tracking Bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
index e9b8908..496c64e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
@@ -17,6 +17,14 @@
 
 package com.android.systemui.keyboard
 
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl
+import dagger.Binds
 import dagger.Module
 
-@Module abstract class KeyboardModule
+@Module
+abstract class KeyboardModule {
+
+    @Binds
+    abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt
new file mode 100644
index 0000000..ea15a9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.keyboard.data.model
+
+/**
+ * Model for current state of keyboard backlight brightness. [level] indicates current level of
+ * backlight brightness and [maxLevel] its max possible value.
+ */
+data class BacklightModel(val level: Int, val maxLevel: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
new file mode 100644
index 0000000..70faf40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.keyboard.data.repository
+
+import android.hardware.input.InputManager
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.data.model.BacklightModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+interface KeyboardRepository {
+    val keyboardConnected: Flow<Boolean>
+    val backlight: Flow<BacklightModel>
+}
+
+@SysUISingleton
+class KeyboardRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val inputManager: InputManager,
+) : KeyboardRepository {
+
+    private val connectedDeviceIds: Flow<Set<Int>> =
+        conflatedCallbackFlow {
+                fun send(element: Set<Int>) = trySendWithFailureLogging(element, TAG)
+
+                var connectedKeyboards = inputManager.inputDeviceIds.toSet()
+                val listener =
+                    object : InputManager.InputDeviceListener {
+                        override fun onInputDeviceAdded(deviceId: Int) {
+                            connectedKeyboards = connectedKeyboards + deviceId
+                            send(connectedKeyboards)
+                        }
+
+                        override fun onInputDeviceChanged(deviceId: Int) = Unit
+
+                        override fun onInputDeviceRemoved(deviceId: Int) {
+                            connectedKeyboards = connectedKeyboards - deviceId
+                            send(connectedKeyboards)
+                        }
+                    }
+                send(connectedKeyboards)
+                inputManager.registerInputDeviceListener(listener, /* handler= */ null)
+                awaitClose { inputManager.unregisterInputDeviceListener(listener) }
+            }
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.Lazily,
+                replay = 1,
+            )
+
+    override val keyboardConnected: Flow<Boolean> =
+        connectedDeviceIds
+            .map { it.any { deviceId -> isPhysicalFullKeyboard(deviceId) } }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
+    override val backlight: Flow<BacklightModel> =
+        conflatedCallbackFlow {
+            // TODO(b/268645734) register BacklightListener
+        }
+
+    private fun isPhysicalFullKeyboard(deviceId: Int): Boolean {
+        val device = inputManager.getInputDevice(deviceId)
+        return !device.isVirtual && device.isFullKeyboard
+    }
+
+    companion object {
+        const val TAG = "KeyboardRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 7a891b0..eef7ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -602,6 +602,7 @@
             checkPermission();
             mKeyguardViewMediator.onScreenTurnedOff();
             mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_OFF);
+            mScreenOnCoordinator.onScreenTurnedOff();
         }
 
         @Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 54fc5b5..02bee3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -59,6 +59,7 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.SoundPool;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.DeadObjectException;
 import android.os.Handler;
@@ -67,6 +68,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -102,6 +104,7 @@
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardConstants;
@@ -270,6 +273,8 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
+    private final IStatusBarService mStatusBarService;
+    private final IBinder mStatusBarDisableToken = new Binder();
     private final UserTracker mUserTracker;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final Executor mUiBgExecutor;
@@ -1213,6 +1218,8 @@
         mPM = powerManager;
         mTrustManager = trustManager;
         mUserSwitcherController = userSwitcherController;
+        mStatusBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mKeyguardDisplayManager = keyguardDisplayManager;
         mShadeController = shadeControllerLazy;
         dumpManager.registerDumpable(getClass().getName(), this);
@@ -2918,7 +2925,12 @@
             // TODO (b/155663717) After restart, status bar will not properly hide home button
             //  unless disable is called to show un-hide it once first
             if (forceClearFlags) {
-                mStatusBarManager.disable(flags);
+                try {
+                    mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+                            mContext.getPackageName(), mUserTracker.getUserId());
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Failed to force clear flags", e);
+                }
             }
 
             if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) {
@@ -2934,7 +2946,12 @@
                         +  " --> flags=0x" + Integer.toHexString(flags));
             }
 
-            mStatusBarManager.disable(flags);
+            try {
+                mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+                        mContext.getPackageName(), mUserTracker.getUserId());
+            } catch (RemoteException e) {
+                Log.d(TAG, "Failed to set disable flags: " + flags, e);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 4331fe6..0e85347 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -60,7 +60,6 @@
      */
     val panelExpansionAmount: StateFlow<Float>
     val keyguardPosition: StateFlow<Float>
-    val onScreenTurnedOff: StateFlow<Boolean>
     val isBackButtonEnabled: StateFlow<Boolean?>
     /** Determines if user is already unlocked */
     val keyguardAuthenticated: StateFlow<Boolean?>
@@ -70,6 +69,8 @@
     val bouncerErrorMessage: CharSequence?
     val alternateBouncerVisible: StateFlow<Boolean>
     val alternateBouncerUIAvailable: StateFlow<Boolean>
+    val sideFpsShowing: StateFlow<Boolean>
+
     var lastAlternateBouncerVisibleTime: Long
 
     fun setPrimaryScrimmed(isScrimmed: Boolean)
@@ -98,11 +99,11 @@
 
     fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
 
-    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)
-
     fun setAlternateVisible(isVisible: Boolean)
 
     fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
+
+    fun setSideFpsShowing(isShowing: Boolean)
 }
 
 @SysUISingleton
@@ -142,8 +143,6 @@
     override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
     private val _keyguardPosition = MutableStateFlow(0f)
     override val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _onScreenTurnedOff = MutableStateFlow(false)
-    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
     override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
@@ -165,6 +164,8 @@
     private val _alternateBouncerUIAvailable = MutableStateFlow(false)
     override val alternateBouncerUIAvailable: StateFlow<Boolean> =
         _alternateBouncerUIAvailable.asStateFlow()
+    private val _sideFpsShowing = MutableStateFlow(false)
+    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
 
     init {
         setUpLogging()
@@ -235,8 +236,8 @@
         _isBackButtonEnabled.value = isBackButtonEnabled
     }
 
-    override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
-        _onScreenTurnedOff.value = onScreenTurnedOff
+    override fun setSideFpsShowing(isShowing: Boolean) {
+        _sideFpsShowing.value = isShowing
     }
 
     /** Sets up logs for state flows. */
@@ -276,9 +277,6 @@
             .map { it.toInt() }
             .logDiffsForTable(buffer, "", "KeyguardPosition", -1)
             .launchIn(applicationScope)
-        onScreenTurnedOff
-            .logDiffsForTable(buffer, "", "OnScreenTurnedOff", false)
-            .launchIn(applicationScope)
         isBackButtonEnabled
             .filterNotNull()
             .logDiffsForTable(buffer, "", "IsBackButtonEnabled", false)
@@ -293,6 +291,9 @@
         alternateBouncerUIAvailable
             .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
             .launchIn(applicationScope)
+        sideFpsShowing
+            .logDiffsForTable(buffer, "", "isSideFpsShowing", false)
+            .launchIn(applicationScope)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index dfbe1c2..9b5f7f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -410,16 +411,10 @@
         )
     }
 
-    private suspend fun isFeatureDisabledByDevicePolicy(): Boolean {
-        val flags =
-            withContext(backgroundDispatcher) {
-                devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
-            }
-        val flagsToCheck =
-            DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or
-                DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
-        return flagsToCheck and flags != 0
-    }
+    private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
+        withContext(backgroundDispatcher) {
+            devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+        }
 
     companion object {
         private const val TAG = "KeyguardQuickAffordanceInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index e819da9..edd2897 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -16,14 +16,19 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.content.Context
 import android.content.res.ColorStateList
 import android.hardware.biometrics.BiometricSourceType
 import android.os.Handler
 import android.os.Trace
 import android.view.View
+import android.util.Log
+import com.android.keyguard.KeyguardConstants
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.DejankUtils
+import com.android.systemui.R
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -60,8 +65,9 @@
     private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
     private val falsingCollector: FalsingCollector,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
+    private val context: Context,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     keyguardBypassController: KeyguardBypassController,
-    keyguardUpdateMonitor: KeyguardUpdateMonitor,
 ) {
     /** Whether we want to wait for face auth. */
     private val primaryBouncerFaceDelay =
@@ -88,7 +94,6 @@
     }
 
     val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
-    val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
     val show: Flow<KeyguardBouncerModel> = repository.primaryBouncerShow.filterNotNull()
     val hide: Flow<Unit> = repository.primaryBouncerHide.filter { it }.map {}
     val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
@@ -113,6 +118,24 @@
         }
     /** Allow for interaction when just about fully visible */
     val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
+    val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
+
+    init {
+        keyguardUpdateMonitor.registerCallback(
+            object : KeyguardUpdateMonitorCallback() {
+                override fun onBiometricRunningStateChanged(
+                    running: Boolean,
+                    biometricSourceType: BiometricSourceType?
+                ) {
+                    updateSideFpsVisibility()
+                }
+
+                override fun onStrongAuthStateChanged(userId: Int) {
+                    updateSideFpsVisibility()
+                }
+            }
+        )
+    }
 
     // TODO(b/243685699): Move isScrimmed logic to data layer.
     // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
@@ -120,7 +143,6 @@
     @JvmOverloads
     fun show(isScrimmed: Boolean) {
         // Reset some states as we show the bouncer.
-        repository.setOnScreenTurnedOff(false)
         repository.setKeyguardAuthenticated(null)
         repository.setPrimaryHide(false)
         repository.setPrimaryStartingToHide(false)
@@ -254,11 +276,6 @@
         repository.setKeyguardAuthenticated(strongAuth)
     }
 
-    /** Tell the bouncer the screen has turned off. */
-    fun onScreenTurnedOff() {
-        repository.setOnScreenTurnedOff(true)
-    }
-
     /** Update the position of the bouncer when showing. */
     fun setKeyguardPosition(position: Float) {
         repository.setKeyguardPosition(position)
@@ -293,6 +310,35 @@
         repository.setPrimaryStartDisappearAnimation(finishRunnable)
     }
 
+    /** Determine whether to show the side fps animation. */
+    fun updateSideFpsVisibility() {
+        val sfpsEnabled: Boolean =
+            context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
+        val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
+        val isUnlockingWithFpAllowed: Boolean =
+            keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+        val bouncerVisible = repository.primaryBouncerVisible.value
+        val toShow =
+            (repository.primaryBouncerVisible.value &&
+                sfpsEnabled &&
+                fpsDetectionRunning &&
+                isUnlockingWithFpAllowed &&
+                !isAnimatingAway())
+
+        if (KeyguardConstants.DEBUG) {
+            Log.d(
+                TAG,
+                ("sideFpsToShow=$toShow\n" +
+                    "bouncerVisible=$bouncerVisible\n" +
+                    "configEnabled=$sfpsEnabled\n" +
+                    "fpsDetectionRunning=$fpsDetectionRunning\n" +
+                    "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" +
+                    "isAnimatingAway=${isAnimatingAway()}")
+            )
+        }
+        repository.setSideFpsShowing(toShow)
+    }
+
     /** Returns whether bouncer is fully showing. */
     fun isFullyShowing(): Boolean {
         return (repository.primaryBouncerShowingSoon.value ||
@@ -336,4 +382,8 @@
         DejankUtils.removeCallbacks(showRunnable)
         mainHandler.removeCallbacks(showRunnable)
     }
+
+    companion object {
+        private const val TAG = "PrimaryBouncerInteractor"
+    }
 }
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 56f911f..7db567b 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,6 +34,7 @@
 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
 
@@ -121,7 +122,6 @@
                     launch {
                         viewModel.hide.collect {
                             securityContainerController.cancelDismissAction()
-                            securityContainerController.onPause()
                             securityContainerController.reset()
                         }
                     }
@@ -155,13 +155,18 @@
 
                     launch {
                         viewModel.isBouncerVisible.collect { isVisible ->
-                            val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
-                            view.visibility = visibility
-                            securityContainerController.onBouncerVisibilityChanged(visibility)
+                            view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+                            securityContainerController.onBouncerVisibilityChanged(isVisible)
                         }
                     }
 
                     launch {
+                        viewModel.isBouncerVisible
+                            .filter { !it }
+                            .collect { securityContainerController.onPause() }
+                    }
+
+                    launch {
                         viewModel.isInteractable.collect { isInteractable ->
                             securityContainerController.setInteractable(isInteractable)
                         }
@@ -204,10 +209,14 @@
                     }
 
                     launch {
-                        viewModel.screenTurnedOff.collect {
-                            if (view.visibility == View.VISIBLE) {
-                                securityContainerController.onPause()
-                            }
+                        viewModel.shouldUpdateSideFps.collect {
+                            viewModel.updateSideFpsVisibility()
+                        }
+                    }
+
+                    launch {
+                        viewModel.sideFpsShowing.collect {
+                            securityContainerController.updateSideFpsVisibility(it)
                         }
                     }
                     awaitCancellation()
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 b8b3a8e..97e94d8f3 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
@@ -24,7 +24,9 @@
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 
 /** Models UI state for the lock screen bouncer; handles user input. */
 class KeyguardBouncerViewModel
@@ -66,8 +68,16 @@
     /** Observe whether keyguard is authenticated already. */
     val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
 
-    /** Observe whether screen is turned off. */
-    val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff
+    /** Observe whether the side fps is showing. */
+    val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
+
+    /** Observe whether we should update fps is showing. */
+    val shouldUpdateSideFps: Flow<Unit> =
+        merge(
+            interactor.startingToHide,
+            interactor.isVisible.map {},
+            interactor.startingDisappearAnimation.filterNotNull().map {}
+        )
 
     /** Observe whether we want to update resources. */
     fun notifyUpdateResources() {
@@ -84,6 +94,10 @@
         interactor.onMessageShown()
     }
 
+    fun updateSideFpsVisibility() {
+        interactor.updateSideFpsVisibility()
+    }
+
     /** Observe whether back button is enabled. */
     fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
         return interactor.isBackButtonEnabled.map { enabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
index dc7a4f1..0b57175 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
@@ -88,6 +88,8 @@
     }
 }
 
+/** Key to indicate whether this card should be used to re-show recent media */
+const val EXTRA_KEY_TRIGGER_RESUME = "SHOULD_TRIGGER_RESUME"
 /** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */
 const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE"
 /** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 27f7b97..97717a6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -138,14 +139,23 @@
         val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 })
         val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
         var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE
-        data.cardAction?.let {
-            val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
+        data.cardAction?.extras?.let {
+            val smartspaceMaxAgeSeconds = it.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
             if (smartspaceMaxAgeSeconds > 0) {
                 smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds)
             }
         }
 
-        val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive
+        // Check if smartspace has explicitly specified whether to re-activate resumable media.
+        // The default behavior is to trigger if the smartspace data is active.
+        val shouldTriggerResume =
+            if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) {
+                data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true)
+            } else {
+                true
+            }
+        val shouldReactivate =
+            shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive
 
         if (timeSinceActive < smartspaceMaxAgeMillis) {
             // It could happen there are existing active media resume cards, then we don't need to
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 4ff082a..0b0535d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -98,7 +98,7 @@
     // Calculates the actual starting percentage according to ripple shader progress set method.
     // Check calculations in [RippleShader.progress]
     fun calculateStartingPercentage(newHeight: Float): Float {
-        val ratio = rippleShader.currentHeight / newHeight
+        val ratio = rippleShader.rippleSize.currentHeight / newHeight
         val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
         return 1 - remainingPercentage
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 389034a..f3d6014 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -580,7 +580,7 @@
             }
 
             // Register input event receiver
-            mInputMonitor = InputManager.getInstance().monitorGestureInput(
+            mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
                     "edge-swipe", mDisplayId);
             mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
                     mInputMonitor.getInputChannel(), Looper.getMainLooper(),
@@ -1039,7 +1039,7 @@
                 InputDevice.SOURCE_KEYBOARD);
 
         ev.setDisplayId(mContext.getDisplay().getDisplayId());
-        return InputManager.getInstance()
+        return mContext.getSystemService(InputManager.class)
                 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index be615d6..5702681 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,17 +17,21 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.app.admin.DevicePolicyManager
 import android.content.ActivityNotFoundException
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.os.Build
 import android.os.UserManager
 import android.util.Log
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -49,8 +53,10 @@
     private val optionalBubbles: Optional<Bubbles>,
     private val optionalKeyguardManager: Optional<KeyguardManager>,
     private val optionalUserManager: Optional<UserManager>,
+    private val devicePolicyManager: DevicePolicyManager,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
     private val uiEventLogger: UiEventLogger,
+    private val userTracker: UserTracker,
 ) {
 
     /**
@@ -80,6 +86,18 @@
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
+        val isKeyguardLocked = keyguardManager.isKeyguardLocked
+        // KeyguardQuickAffordanceInteractor blocks the quick affordance from showing in the
+        // keyguard if it is not allowed by the admin policy. Here we block any other way to show
+        // note task when the screen is locked.
+        if (
+            isKeyguardLocked &&
+                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+        ) {
+            logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
+            return
+        }
+
         val noteTaskInfo = resolver.resolveInfo() ?: return
 
         uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
@@ -87,7 +105,7 @@
         // TODO(b/266686199): We should handle when app not available. For now, we log.
         val intent = noteTaskInfo.toCreateNoteIntent()
         try {
-            if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
+            if (isInMultiWindowMode || isKeyguardLocked) {
                 context.startActivity(intent)
             } else {
                 bubbles.showOrHideAppBubble(intent)
@@ -144,7 +162,7 @@
     }
 
     companion object {
-        private val TAG = NoteTaskController::class.simpleName.orEmpty()
+        val TAG = NoteTaskController::class.simpleName.orEmpty()
 
         private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
             return Intent(ACTION_CREATE_NOTE)
@@ -165,3 +183,9 @@
         const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
     }
 }
+
+private inline fun logDebug(message: () -> String) {
+    if (Build.IS_DEBUGGABLE) {
+        Log.d(NoteTaskController.TAG, message())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index 245cf89..2751072 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -26,7 +26,10 @@
     @Inject
     public ProcessWrapper() {}
 
-    public int getUserHandleIdentifier() {
-        return android.os.Process.myUserHandle().getIdentifier();
+    /**
+     * Returns {@code true} if System User is running the current process.
+     */
+    public boolean isSystemUser() {
+        return android.os.Process.myUserHandle().isSystem();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java
rename to packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
index 5a21ea0..80fbf911 100644
--- a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
@@ -17,29 +17,26 @@
 package com.android.systemui.process.condition;
 
 import com.android.systemui.process.ProcessWrapper;
-import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.condition.Condition;
 
 import javax.inject.Inject;
 
 /**
- * {@link UserProcessCondition} provides a signal when the process handle belongs to the current
- * user.
+ * {@link SystemProcessCondition} checks to make sure the current process is being ran by the
+ * System User.
  */
-public class UserProcessCondition extends Condition {
+public class SystemProcessCondition extends Condition {
     private final ProcessWrapper mProcessWrapper;
-    private final UserTracker mUserTracker;
 
     @Inject
-    public UserProcessCondition(ProcessWrapper processWrapper, UserTracker userTracker) {
+    public SystemProcessCondition(ProcessWrapper processWrapper) {
+        super();
         mProcessWrapper = processWrapper;
-        mUserTracker = userTracker;
     }
 
     @Override
     protected void start() {
-        updateCondition(mUserTracker.getUserId()
-                == mProcessWrapper.getUserHandleIdentifier());
+        updateCondition(mProcessWrapper.isSystemUser());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index f4640ad..cda98dc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1197,6 +1197,7 @@
     }
 
     private void onSplitShadeEnabledChanged() {
+        mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
         // when we switch between split shade and regular shade we want to enforce setting qs to
         // the default state: expanded for split shade and collapsed otherwise
         if (!isOnKeyguard() && mPanelExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index d041212..43da50a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -1710,12 +1710,16 @@
      */
     private void flingQs(float vel, int type, final Runnable onFinishRunnable,
             boolean isClick) {
+        mShadeLog.flingQs(type, isClick);
         float target;
         switch (type) {
             case FLING_EXPAND:
                 target = getMaxExpansionHeight();
                 break;
             case FLING_COLLAPSE:
+                if (mSplitShadeEnabled) { // TODO:(b/269742565) remove below log
+                    Log.wtfStack(TAG, "FLING_COLLAPSE called in split shade");
+                }
                 target = getMinExpansionHeight();
                 break;
             case FLING_HIDE:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index aa8c5b6..d34e127 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -20,6 +20,9 @@
 import com.android.systemui.log.dagger.ShadeLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE
+import com.android.systemui.shade.NotificationPanelViewController.FLING_EXPAND
+import com.android.systemui.shade.NotificationPanelViewController.FLING_HIDE
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
@@ -241,18 +244,40 @@
         )
     }
 
-    fun logLastFlingWasExpanding(
-            expand: Boolean
-    ) {
+    fun logLastFlingWasExpanding(expand: Boolean) {
         buffer.log(
-                TAG,
-                LogLevel.VERBOSE,
-                {
-                    bool1 = expand
-                },
-                {
-                    "NPVC mLastFlingWasExpanding set to: $bool1"
-                }
+            TAG,
+            LogLevel.VERBOSE,
+            { bool1 = expand },
+            { "NPVC mLastFlingWasExpanding set to: $bool1" }
+        )
+    }
+
+    fun flingQs(flingType: Int, isClick: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = flingTypeToString(flingType)
+                bool1 = isClick
+            },
+            { "QS fling with type $str1, originated from click: $isClick" }
+        )
+    }
+
+    private fun flingTypeToString(flingType: Int) = when (flingType) {
+        FLING_EXPAND -> "FLING_EXPAND"
+        FLING_COLLAPSE -> "FLING_COLLAPSE"
+        FLING_HIDE -> "FLING_HIDE"
+        else -> "UNKNOWN"
+    }
+
+    fun logSplitShadeChanged(splitShadeEnabled: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            { bool1 = splitShadeEnabled },
+            { "Split shade state changed: split shade ${if (bool1) "enabled" else "disabled"}" }
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 1e4f5bb..84b40e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -298,9 +298,7 @@
             mLogger.logUpdateEntry(mEntry, updatePostTime);
 
             final long now = mClock.currentTimeMillis();
-            mEarliestRemovaltime = isSticky()
-                    ? mEntry.mCreationElapsedRealTime + mStickyDisplayTime
-                    : now + mMinimumDisplayTime;
+            mEarliestRemovaltime = now + mMinimumDisplayTime;
 
             if (updatePostTime) {
                 mPostTime = Math.max(mPostTime, now);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index f20f929..43fbc7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -356,7 +356,7 @@
      * Keyboard with its default map.
      */
     private void retrieveKeyCharacterMap(int deviceId) {
-        final InputManager inputManager = InputManager.getInstance();
+        final InputManager inputManager = mContext.getSystemService(InputManager.class);
         mBackupKeyCharacterMap = inputManager.getInputDevice(-1).getKeyCharacterMap();
         if (deviceId != -1) {
             final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 93aff7a..f395bea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -52,18 +52,19 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.WeatherData
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.shared.regionsampling.UpdateColorCallback
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
-import com.android.systemui.plugins.Weather
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.time.Instant
 import java.util.Optional
@@ -81,6 +82,7 @@
         private val smartspaceManager: SmartspaceManager,
         private val activityStarter: ActivityStarter,
         private val falsingManager: FalsingManager,
+        private val systemClock: SystemClock,
         private val secureSettings: SecureSettings,
         private val userTracker: UserTracker,
         private val contentResolver: ContentResolver,
@@ -153,6 +155,18 @@
 
         // The weather data plugin takes unfiltered targets and performs the filtering internally.
         weatherPlugin?.onTargetsAvailable(targets)
+        val now = Instant.ofEpochMilli(systemClock.currentTimeMillis())
+        val weatherTarget = targets.find { t ->
+            t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
+                    now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) &&
+                    now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
+        }
+        if (weatherTarget != null) {
+            val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras)
+            if (weatherData != null) {
+                keyguardUpdateMonitor.sendWeatherData(weatherData)
+            }
+        }
 
         val filteredTargets = targets.filter(::filterSmartspaceTarget)
         plugin?.onTargetsAvailable(filteredTargets)
@@ -174,17 +188,6 @@
             }
             isContentUpdatedOnce = true
         }
-
-        val now = Instant.now()
-        val weatherTarget = targets.find { t ->
-            t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
-                    now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) &&
-                    now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
-        }
-        if (weatherTarget != null) {
-            val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras)
-            keyguardUpdateMonitor.sendWeatherData(weatherData)
-        }
     }
 
     private val userTrackerCallback = object : UserTracker.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b115233..183790d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -790,7 +790,7 @@
 
     @Override
     public void onFinishedGoingToSleep() {
-        mPrimaryBouncerInteractor.onScreenTurnedOff();
+        mPrimaryBouncerInteractor.hide();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 559423b..94ce002 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1416,6 +1416,7 @@
             @Override
             public void onAnimationCancel(@NonNull Animator animation) {
                 mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL);
+                Log.d(TAG, "onAnimationCancel");
             }
 
             @Override
@@ -1506,6 +1507,7 @@
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
         if (mIsAnimatingDismiss) {
+            Log.d(TAG, "dismissH: isAnimatingDismiss");
             return;
         }
         mIsAnimatingDismiss = true;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 568e8d2..064bc9c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -32,11 +32,9 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -44,7 +42,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricSourceType;
 import android.media.AudioManager;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
@@ -53,7 +50,6 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.WindowInsetsController;
 import android.widget.FrameLayout;
 
@@ -96,10 +92,8 @@
 @TestableLooper.RunWithLooper()
 public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
     private static final int TARGET_USER_ID = 100;
-
     @Rule
     public MockitoRule mRule = MockitoJUnit.rule();
-
     @Mock
     private KeyguardSecurityContainer mView;
     @Mock
@@ -368,134 +362,12 @@
     }
 
     @Test
-    public void onBouncerVisibilityChanged_allConditionsGood_sideFpsHintShown() {
-        setupConditionsToEnableSideFpsHint();
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-
-        verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
-        verify(mSideFpsController, never()).hide(any());
-    }
-
-    @Test
-    public void onBouncerVisibilityChanged_fpsSensorNotRunning_sideFpsHintHidden() {
-        setupConditionsToEnableSideFpsHint();
-        setFingerprintDetectionRunning(false);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-        verify(mSideFpsController, never()).show(any(), anyInt());
-    }
-
-    @Test
-    public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
-        setupConditionsToEnableSideFpsHint();
-        setSideFpsHintEnabledFromResources(false);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-        verify(mSideFpsController, never()).show(any(), anyInt());
-    }
-
-    @Test
-    public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() {
-        setupConditionsToEnableSideFpsHint();
-        setUnlockingWithFingerprintAllowed(false);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-        verify(mSideFpsController, never()).show(any(), anyInt());
-    }
-
-    @Test
-    public void onBouncerVisibilityChanged_sideFpsHintShown_sideFpsHintHidden() {
-        setupGetSecurityView();
-        setupConditionsToEnableSideFpsHint();
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
-
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-        verify(mSideFpsController, never()).show(any(), anyInt());
-    }
-
-    @Test
     public void onBouncerVisibilityChanged_resetsScale() {
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
-
+        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false);
         verify(mView).resetScale();
     }
 
     @Test
-    public void onStartingToHide_sideFpsHintShown_sideFpsHintHidden() {
-        setupGetSecurityView();
-        setupConditionsToEnableSideFpsHint();
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onStartingToHide();
-
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-        verify(mSideFpsController, never()).show(any(), anyInt());
-    }
-
-    @Test
-    public void onPause_sideFpsHintShown_sideFpsHintHidden() {
-        setupGetSecurityView();
-        setupConditionsToEnableSideFpsHint();
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onPause();
-
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-        verify(mSideFpsController, never()).show(any(), anyInt());
-    }
-
-    @Test
-    public void onResume_sideFpsHintShouldBeShown_sideFpsHintShown() {
-        setupGetSecurityView();
-        setupConditionsToEnableSideFpsHint();
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onResume(0);
-
-        verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
-        verify(mSideFpsController, never()).hide(any());
-    }
-
-    @Test
-    public void onResume_sideFpsHintShouldNotBeShown_sideFpsHintHidden() {
-        setupGetSecurityView();
-        setupConditionsToEnableSideFpsHint();
-        setSideFpsHintEnabledFromResources(false);
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        reset(mSideFpsController);
-
-        mKeyguardSecurityContainerController.onResume(0);
-
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-        verify(mSideFpsController, never()).show(any(), anyInt());
-    }
-
-    @Test
     public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
         // GIVEN the current security method is SimPin
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
@@ -721,39 +593,31 @@
                 any(KeyguardSecurityCallback.class));
     }
 
+    @Test
+    public void testSideFpsControllerShow() {
+        mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
+        verify(mSideFpsController).show(
+                SideFpsUiRequestSource.PRIMARY_BOUNCER,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
+    }
+
+    @Test
+    public void testSideFpsControllerHide() {
+        mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false);
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+    }
+
     private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
         mKeyguardSecurityContainerController.onViewAttached();
         verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
         return mSwipeListenerArgumentCaptor.getValue();
     }
 
-    private void setupConditionsToEnableSideFpsHint() {
-        attachView();
-        setSideFpsHintEnabledFromResources(true);
-        setFingerprintDetectionRunning(true);
-        setUnlockingWithFingerprintAllowed(true);
-    }
-
     private void attachView() {
         mKeyguardSecurityContainerController.onViewAttached();
         verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture());
     }
 
-    private void setFingerprintDetectionRunning(boolean running) {
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(running);
-        mKeyguardUpdateMonitorCallback.getValue().onBiometricRunningStateChanged(running,
-                BiometricSourceType.FINGERPRINT);
-    }
-
-    private void setSideFpsHintEnabledFromResources(boolean enabled) {
-        mTestableResources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer,
-                enabled);
-    }
-
-    private void setUnlockingWithFingerprintAllowed(boolean allowed) {
-        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed);
-    }
-
     private void setupGetSecurityView() {
         when(mKeyguardSecurityViewFlipperController.getSecurityView(
                 any(), any(KeyguardSecurityCallback.class)))
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fec34c2..e5d0692a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -49,6 +49,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 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.never;
 import static org.mockito.Mockito.spy;
@@ -2415,8 +2416,6 @@
     @Test
     public void testUsbComplianceIntent_refreshBatteryInfo() {
         Context contextSpy = getSpyContext();
-        when(contextSpy.registerReceiver(eq(null), any(IntentFilter.class)))
-                .thenReturn(getBatteryIntent());
 
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(
                 contextSpy, new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED));
@@ -2429,8 +2428,6 @@
     public void testUsbComplianceIntent_refreshBatteryInfoWithIncompatibleCharger() {
         Context contextSpy = getSpyContext();
         setupIncompatibleCharging();
-        when(contextSpy.registerReceiver(eq(null), any(IntentFilter.class)))
-                .thenReturn(getBatteryIntent());
 
         mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(
                 contextSpy, new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED));
@@ -2740,10 +2737,10 @@
     }
 
     private Context getSpyContext() {
+        mContext.addMockSystemService(UsbManager.class, mUsbManager);
         Context contextSpy = spy(mContext);
-        when(contextSpy.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
-        when(contextSpy.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)))
-                .thenReturn(new Intent(Intent.ACTION_BATTERY_CHANGED));
+        doReturn(getBatteryIntent()).when(contextSpy).registerReceiver(eq(null),
+                any(IntentFilter.class));
         return contextSpy;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index e9a2789..9fe32f1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
@@ -32,6 +32,7 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.Optional
@@ -83,6 +84,33 @@
     }
 
     @Test
+    fun testTasksReady_onScreenTurningOnAndTurnedOnEventsCalledTogether_callsDrawnCallback() {
+        screenOnCoordinator.onScreenTurningOn(runnable)
+        screenOnCoordinator.onScreenTurnedOn()
+
+        onUnfoldOverlayReady()
+        onFoldAodReady()
+        waitHandlerIdle(testHandler)
+
+        // Should be called when both unfold overlay and keyguard drawn ready
+        verify(runnable).run()
+    }
+
+    @Test
+    fun testTasksReady_onScreenTurnedOnAndTurnedOffBeforeCompletion_doesNotCallDrawnCallback() {
+        screenOnCoordinator.onScreenTurningOn(runnable)
+        screenOnCoordinator.onScreenTurnedOn()
+        screenOnCoordinator.onScreenTurnedOff()
+
+        onUnfoldOverlayReady()
+        onFoldAodReady()
+        waitHandlerIdle(testHandler)
+
+        // Should not be called because this screen turning on call is not valid anymore
+        verify(runnable, never()).run()
+    }
+
+    @Test
     fun testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() {
         // Recreate with empty unfoldComponent
         screenOnCoordinator = ScreenOnCoordinator(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index c73ff1d..54c9d39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -86,8 +86,9 @@
                 mock(PrimaryBouncerCallbackInteractor::class.java),
                 mock(FalsingCollector::class.java),
                 mock(DismissCallbackRegistry::class.java),
+                context,
+                mKeyguardUpdateMonitor,
                 mock(KeyguardBypassController::class.java),
-                mKeyguardUpdateMonitor
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
new file mode 100644
index 0000000..34d5661
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+import androidx.test.filters.SmallTest
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class DevicePolicyManagerExtTest {
+
+    @Mock lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var userTracker: UserTracker
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(userTracker.userId).thenReturn(CURRENT_USER_ID)
+    }
+
+    // region areKeyguardShortcutsDisabled
+    @Test
+    fun areKeyguardShortcutsDisabled_noDisabledKeyguardFeature_shouldReturnFalse() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isFalse()
+    }
+
+    @Test
+    fun areKeyguardShortcutsDisabled_otherDisabledKeyguardFeatures_shouldReturnFalse() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_SECURE_CAMERA or KEYGUARD_DISABLE_FACE)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isFalse()
+    }
+
+    @Test
+    fun areKeyguardShortcutsDisabled_disabledShortcutsKeyguardFeature_shouldReturnTrue() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isTrue()
+    }
+
+    @Test
+    fun areKeyguardShortcutsDisabled_disabledAllKeyguardFeatures_shouldReturnTrue() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_FEATURES_ALL)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isTrue()
+    }
+    // endregion
+
+    private companion object {
+        const val CURRENT_USER_ID = 123
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
new file mode 100644
index 0000000..f6ff4b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.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.keyboard.data.repository
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.StandardTestDispatcher
+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
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyboardRepositoryTest : SysuiTestCase() {
+
+    @Captor
+    private lateinit var deviceListenerCaptor: ArgumentCaptor<InputManager.InputDeviceListener>
+    @Mock private lateinit var inputManager: InputManager
+
+    private lateinit var underTest: KeyboardRepository
+    private lateinit var dispatcher: CoroutineDispatcher
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
+        whenever(inputManager.getInputDevice(any())).then { invocation ->
+            val id = invocation.arguments.first()
+            INPUT_DEVICES_MAP[id]
+        }
+        dispatcher = StandardTestDispatcher()
+        testScope = TestScope(dispatcher)
+        underTest = KeyboardRepositoryImpl(testScope.backgroundScope, dispatcher, inputManager)
+    }
+
+    @Test
+    fun emitsDisconnected_ifNothingIsConnected() =
+        testScope.runTest {
+            val initialState = underTest.keyboardConnected.first()
+            assertThat(initialState).isFalse()
+        }
+
+    @Test
+    fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() =
+        testScope.runTest {
+            whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID))
+            val initialValue = underTest.keyboardConnected.first()
+            assertThat(initialValue).isTrue()
+        }
+
+    @Test
+    fun emitsConnected_whenNewPhysicalKeyboardConnects() =
+        testScope.runTest {
+            val deviceListener = captureDeviceListener()
+            val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
+
+            deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+
+            assertThat(isKeyboardConnected).isTrue()
+        }
+
+    @Test
+    fun emitsDisconnected_whenKeyboardDisconnects() =
+        testScope.runTest {
+            val deviceListener = captureDeviceListener()
+            val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
+
+            deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+            assertThat(isKeyboardConnected).isTrue()
+
+            deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
+            assertThat(isKeyboardConnected).isFalse()
+        }
+
+    private suspend fun captureDeviceListener(): InputManager.InputDeviceListener {
+        underTest.keyboardConnected.first()
+        verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable())
+        return deviceListenerCaptor.value
+    }
+
+    @Test
+    fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() =
+        testScope.runTest {
+            val deviceListener = captureDeviceListener()
+            val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
+
+            deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID)
+            assertThat(isKeyboardConnected).isFalse()
+
+            deviceListener.onInputDeviceAdded(VIRTUAL_FULL_KEYBOARD_ID)
+            assertThat(isKeyboardConnected).isFalse()
+        }
+
+    @Test
+    fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() =
+        testScope.runTest {
+            val deviceListener = captureDeviceListener()
+            val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
+
+            deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
+            assertThat(isKeyboardConnected).isFalse()
+        }
+
+    @Test
+    fun emitsConnected_whenAnotherDeviceDisconnects() =
+        testScope.runTest {
+            val deviceListener = captureDeviceListener()
+            val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
+
+            deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+            deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID)
+
+            assertThat(isKeyboardConnected).isTrue()
+        }
+
+    @Test
+    fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() =
+        testScope.runTest {
+            val deviceListener = captureDeviceListener()
+            val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
+
+            deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+            deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+            deviceListener.onInputDeviceRemoved(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+
+            assertThat(isKeyboardConnected).isTrue()
+        }
+
+    @Test
+    fun passesKeyboardBacklightValues_fromBacklightListener() {
+        // TODO(b/268645734): implement when implementing backlight listener
+    }
+
+    private companion object {
+        private const val PHYSICAL_FULL_KEYBOARD_ID = 1
+        private const val VIRTUAL_FULL_KEYBOARD_ID = 2
+        private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3
+        private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4
+
+        private val INPUT_DEVICES_MAP: Map<Int, InputDevice> =
+            mapOf(
+                PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true),
+                VIRTUAL_FULL_KEYBOARD_ID to inputDevice(virtual = true, fullKeyboard = true),
+                PHYSICAL_NOT_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = false),
+                ANOTHER_PHYSICAL_FULL_KEYBOARD_ID to
+                    inputDevice(virtual = false, fullKeyboard = true)
+            )
+
+        private fun inputDevice(virtual: Boolean, fullKeyboard: Boolean): InputDevice =
+            mock<InputDevice>().also {
+                whenever(it.isVirtual).thenReturn(virtual)
+                whenever(it.isFullKeyboard).thenReturn(fullKeyboard)
+            }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 46ed829..6b7fd61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -19,11 +19,13 @@
 import android.os.Looper
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.DejankUtils
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -69,6 +71,7 @@
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     private lateinit var underTest: PrimaryBouncerInteractor
+    private lateinit var resources: TestableResources
 
     @Before
     fun setUp() {
@@ -84,18 +87,19 @@
                 mPrimaryBouncerCallbackInteractor,
                 falsingCollector,
                 dismissCallbackRegistry,
-                keyguardBypassController,
+                context,
                 keyguardUpdateMonitor,
+                keyguardBypassController,
             )
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         `when`(repository.primaryBouncerShow.value).thenReturn(null)
         `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+        resources = context.orCreateTestableResources
     }
 
     @Test
     fun testShow_isScrimmed() {
         underTest.show(true)
-        verify(repository).setOnScreenTurnedOff(false)
         verify(repository).setKeyguardAuthenticated(null)
         verify(repository).setPrimaryHide(false)
         verify(repository).setPrimaryStartingToHide(false)
@@ -207,12 +211,6 @@
     }
 
     @Test
-    fun testOnScreenTurnedOff() {
-        underTest.onScreenTurnedOff()
-        verify(repository).setOnScreenTurnedOff(true)
-    }
-
-    @Test
     fun testSetKeyguardPosition() {
         underTest.setKeyguardPosition(0f)
         verify(repository).setKeyguardPosition(0f)
@@ -286,4 +284,98 @@
         `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
         assertThat(underTest.willDismissWithAction()).isFalse()
     }
+
+    @Test
+    fun testSideFpsVisibility() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(true)
+    }
+
+    @Test
+    fun testSideFpsVisibility_notVisible() {
+        updateSideFpsVisibilityParameters(
+            isVisible = false,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_sfpsNotEnabled() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = false,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_fpsDetectionNotRunning() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = false,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = false,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_AnimatingAway() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = true
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    private fun updateSideFpsVisibilityParameters(
+        isVisible: Boolean,
+        sfpsEnabled: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+        isAnimatingAway: Boolean
+    ) {
+        `when`(repository.primaryBouncerVisible.value).thenReturn(isVisible)
+        resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
+        `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(fpsDetectionRunning)
+        `when`(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value)
+            .thenReturn(if (isAnimatingAway) Runnable {} else null)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index 75b74b0..f675e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -44,7 +43,6 @@
 class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
     private lateinit var repository: FakeKeyguardBouncerRepository
     @Mock private lateinit var bouncerView: BouncerView
-    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
     @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
@@ -69,8 +67,9 @@
                 primaryBouncerCallbackInteractor,
                 falsingCollector,
                 dismissCallbackRegistry,
-                keyguardBypassController,
+                context,
                 keyguardUpdateMonitor,
+                keyguardBypassController,
             )
     }
 
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 586af62..65e4c10 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
@@ -16,15 +16,23 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.os.Looper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runCurrent
@@ -33,7 +41,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -41,31 +48,69 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardBouncerViewModelTest : SysuiTestCase() {
     lateinit var underTest: KeyguardBouncerViewModel
+    lateinit var bouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var bouncerView: BouncerView
-    @Mock lateinit var bouncerInteractor: PrimaryBouncerInteractor
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    val repository = FakeKeyguardBouncerRepository()
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        bouncerInteractor =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                primaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                keyguardBypassController,
+            )
         underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
     }
 
     @Test
-    fun setMessage() =
-        runTest {
-            val flow = MutableStateFlow<BouncerShowMessageModel?>(null)
-            var message: BouncerShowMessageModel? = null
-            Mockito.`when`(bouncerInteractor.showMessage)
-                .thenReturn(flow as Flow<BouncerShowMessageModel>)
-            // Reinitialize the view model.
-            underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+    fun setMessage() = runTest {
+        var message: BouncerShowMessageModel? = null
+        val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
 
-            flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null)
+        repository.setShowMessage(BouncerShowMessageModel("abc", null))
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(message?.message).isEqualTo("abc")
+        job.cancel()
+    }
 
-            val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
-            // Run the tasks that are pending at this point of virtual time.
-            runCurrent()
-            assertThat(message?.message).isEqualTo("abc")
-            job.cancel()
-        }
+    @Test
+    fun shouldUpdateSideFps() = runTest {
+        var count = 0
+        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+        repository.setPrimaryVisible(true)
+        // 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)
+        repository.setSideFpsShowing(true)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(sideFpsIsShowing).isEqualTo(true)
+        job.cancel()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index eb6235c..8532ffe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.pipeline
 
 import android.app.smartspace.SmartspaceAction
+import android.os.Bundle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -25,6 +26,7 @@
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.ui.MediaPlayerData
 import com.android.systemui.media.controls.util.MediaFlags
@@ -75,6 +77,7 @@
     @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
     @Mock private lateinit var logger: MediaUiEventLogger
     @Mock private lateinit var mediaFlags: MediaFlags
+    @Mock private lateinit var cardAction: SmartspaceAction
 
     private lateinit var mediaDataFilter: MediaDataFilter
     private lateinit var dataMain: MediaData
@@ -122,6 +125,7 @@
         whenever(smartspaceData.headphoneConnectionTimeMillis)
             .thenReturn(clock.currentTimeMillis() - 100)
         whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
+        whenever(smartspaceData.cardAction).thenReturn(cardAction)
     }
 
     private fun setUser(id: Int) {
@@ -574,4 +578,55 @@
         verify(mediaDataManager, never())
             .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
     }
+
+    @Test
+    fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() {
+        // WHEN we have media that was recently played, but not currently active
+        val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
+
+        // AND we get a smartspace signal with extra to trigger resume
+        val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, true) }
+        whenever(cardAction.extras).thenReturn(extras)
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        // THEN we should tell listeners to treat the media as active instead
+        val dataCurrentAndActive = dataCurrent.copy(active = true)
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY),
+                eq(KEY),
+                eq(dataCurrentAndActive),
+                eq(true),
+                eq(100),
+                eq(true)
+            )
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
+        // And send the smartspace data, but not prioritized
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+    }
+
+    @Test
+    fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() {
+        // WHEN we have media that was recently played, but not currently active
+        val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
+
+        // AND we get a smartspace signal with extra to not trigger resume
+        val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
+        whenever(cardAction.extras).thenReturn(extras)
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        // THEN listeners are not updated to show media
+        verify(listener, never())
+            .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true))
+        // But the smartspace update is still propagated
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 39c4e06..52b29ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.app.admin.DevicePolicyManager
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
@@ -29,6 +30,7 @@
 import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
@@ -39,6 +41,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
@@ -64,6 +67,8 @@
     @Mock lateinit var optionalUserManager: Optional<UserManager>
     @Mock lateinit var userManager: UserManager
     @Mock lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
     @Before
     fun setUp() {
@@ -75,6 +80,13 @@
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
         whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
         whenever(userManager.isUserUnlocked).thenReturn(true)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE)
     }
 
     private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
@@ -84,8 +96,10 @@
             optionalBubbles = optionalBubbles,
             optionalKeyguardManager = optionalKeyguardManager,
             optionalUserManager = optionalUserManager,
+            devicePolicyManager = devicePolicyManager,
             isEnabled = isEnabled,
             uiEventLogger = uiEventLogger,
+            userTracker = userTracker,
         )
     }
 
@@ -291,6 +305,86 @@
     }
     // endregion
 
+    // region keyguard policy
+    @Test
+    fun showNoteTask_keyguardLocked_keyguardDisableShortcutsAll_shouldDoNothing() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
+
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
+    }
+
+    @Test
+    fun showNoteTask_keyguardLocked_keyguardDisableFeaturesAll_shouldDoNothing() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
+
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
+    }
+
+    @Test
+    fun showNoteTask_keyguardUnlocked_keyguardDisableShortcutsAll_shouldStartBubble() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+    }
+
+    @Test
+    fun showNoteTask_keyguardUnlocked_keyguardDisableFeaturesAll_shouldStartBubble() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+    }
+    // endregion
+
     private companion object {
         const val NOTES_PACKAGE_NAME = "com.android.note.app"
         const val NOTES_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
similarity index 70%
rename from packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
index 2293fc5..fb71977 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
@@ -26,7 +26,6 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.process.ProcessWrapper;
-import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.condition.Condition;
 import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -41,10 +40,7 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
-public class UserProcessConditionTest extends SysuiTestCase {
-    @Mock
-    UserTracker mUserTracker;
-
+public class SystemProcessConditionTest extends SysuiTestCase {
     @Mock
     ProcessWrapper mProcessWrapper;
 
@@ -59,15 +55,14 @@
     }
 
     /**
-     * Verifies condition reports false when tracker reports a different user id than the
-     * identifier from the process handle.
+     * Verifies condition reports false when tracker reports the process is being ran by the
+     * system user.
      */
     @Test
-    public void testConditionFailsWithDifferentIds() {
+    public void testConditionFailsWithNonSystemProcess() {
 
-        final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
-        when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
-        when(mUserTracker.getUserId()).thenReturn(1);
+        final Condition condition = new SystemProcessCondition(mProcessWrapper);
+        when(mProcessWrapper.isSystemUser()).thenReturn(false);
 
         final Monitor monitor = new Monitor(mExecutor);
 
@@ -81,15 +76,14 @@
     }
 
     /**
-     * Verifies condition reports false when tracker reports a different user id than the
-     * identifier from the process handle.
+     * Verifies condition reports true when tracker reports the process is being ran by the
+     * system user.
      */
     @Test
-    public void testConditionSucceedsWithSameIds() {
+    public void testConditionSucceedsWithSystemProcess() {
 
-        final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
-        when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
-        when(mUserTracker.getUserId()).thenReturn(0);
+        final Condition condition = new SystemProcessCondition(mProcessWrapper);
+        when(mProcessWrapper.isSystemUser()).thenReturn(true);
 
         final Monitor monitor = new Monitor(mExecutor);
 
@@ -101,5 +95,4 @@
 
         verify(mCallback).onConditionsChanged(true);
     }
-
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 8601d6c..04b372c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -53,6 +54,7 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
+@Ignore("b/269171747")
 public class ReduceBrightColorsTileTest extends SysuiTestCase {
     @Mock
     private QSTileHost mHost;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 413767a..fe18fb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -200,20 +200,6 @@
     }
 
     @Test
-    public void testShowNotification_stickyHun_earliestRemovalTime() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createStickySbn(/* id= */ 0))
-                .build();
-        notifEntry.setCreationElapsedRealTime(0);
-
-        mAlertingNotificationManager.showNotification(notifEntry);
-
-        final long earliestRemovalTime = mAlertingNotificationManager
-                .getCalculatedEarliestRemovalTime(notifEntry.getKey());
-        assertEquals(TEST_STICKY_DISPLAY_TIME, earliestRemovalTime);
-    }
-
-    @Test
     public void testRemoveNotification_removeDeferred() {
         mAlertingNotificationManager.showNotification(mEntry);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 7fdcfb2..2de5705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.lockscreen
 
+import android.app.smartspace.SmartspaceAction
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
 import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener
@@ -26,6 +27,7 @@
 import android.database.ContentObserver
 import android.graphics.drawable.Drawable
 import android.net.Uri
+import android.os.Bundle
 import android.os.Handler
 import android.os.UserHandle
 import android.provider.Settings
@@ -43,6 +45,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.WeatherData
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener
 import com.android.systemui.settings.UserTracker
@@ -54,6 +57,7 @@
 import com.android.systemui.util.concurrency.FakeExecution
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.settings.SecureSettings
@@ -69,6 +73,7 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.Optional
@@ -76,6 +81,13 @@
 
 @SmallTest
 class LockscreenSmartspaceControllerTest : SysuiTestCase() {
+    companion object {
+        const val SMARTSPACE_TIME_TOO_EARLY = 1000L
+        const val SMARTSPACE_TIME_JUST_RIGHT = 4000L
+        const val SMARTSPACE_TIME_TOO_LATE = 9000L
+        const val SMARTSPACE_CREATION_TIME = 1234L
+        const val SMARTSPACE_EXPIRY_TIME = 5678L
+    }
     @Mock
     private lateinit var featureFlags: FeatureFlags
     @Mock
@@ -224,6 +236,7 @@
                 smartspaceManager,
                 activityStarter,
                 falsingManager,
+                clock,
                 secureSettings,
                 userTracker,
                 contentResolver,
@@ -529,6 +542,190 @@
     }
 
     @Test
+    fun testSessionListener_ifWeatherExtraMissing_thenWeatherDataNotSent() {
+        connectSession()
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeTarget(1, userHandlePrimary, isSensitive = true),
+                makeTarget(2, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER)
+
+        )
+        sessionListener.onTargetsAvailable(targets)
+        verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any())
+    }
+
+    @Test
+    fun testSessionListener_ifWeatherExtraIsMissingValues_thenWeatherDataNotSent() {
+        connectSession()
+
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeTarget(1, userHandlePrimary, isSensitive = true),
+                makeWeatherTargetWithExtras(
+                        id = 2,
+                        userHandle = userHandlePrimary,
+                        description = null,
+                        state = WeatherData.WeatherStateIcon.SUNNY.id,
+                        temperature = "32",
+                        useCelsius = null)
+
+        )
+
+        sessionListener.onTargetsAvailable(targets)
+
+        verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any())
+    }
+
+    @Test
+    fun testSessionListener_ifTooEarly_thenWeatherDataNotSent() {
+        connectSession()
+
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_EARLY)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeWeatherTargetWithExtras(
+                        id = 1,
+                        userHandle = userHandleManaged,
+                        description = "Sunny",
+                        state = WeatherData.WeatherStateIcon.SUNNY.id,
+                        temperature = "32",
+                        useCelsius = false)
+        )
+        sessionListener.onTargetsAvailable(targets)
+        verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any())
+    }
+
+    @Test
+    fun testSessionListener_ifOnTime_thenWeatherDataSent() {
+        connectSession()
+
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeWeatherTargetWithExtras(
+                        id = 1,
+                        userHandle = userHandleManaged,
+                        description = "Snow Showers",
+                        state = WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW.id,
+                        temperature = "-1",
+                        useCelsius = false)
+        )
+        sessionListener.onTargetsAvailable(targets)
+        verify(keyguardUpdateMonitor).sendWeatherData(argThat { w ->
+            w.description == "Snow Showers" &&
+                    w.state == WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW &&
+                    w.temperature == -1 && !w.useCelsius
+        })
+    }
+
+    @Test
+    fun testSessionListener_ifTooLate_thenWeatherDataNotSent() {
+        connectSession()
+
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_LATE)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeWeatherTargetWithExtras(
+                        id = 1,
+                        userHandle = userHandleManaged,
+                        description = "Sunny",
+                        state = WeatherData.WeatherStateIcon.SUNNY.id,
+                        temperature = "72",
+                        useCelsius = false)
+        )
+        sessionListener.onTargetsAvailable(targets)
+        verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any())
+    }
+
+    @Test
+    fun testSessionListener_onlyFirstWeatherDataSent() {
+        connectSession()
+
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeWeatherTargetWithExtras(
+                        id = 1,
+                        userHandle = userHandleManaged,
+                        description = "Sunny",
+                        state = WeatherData.WeatherStateIcon.SUNNY.id,
+                        temperature = "72",
+                        useCelsius = false),
+                makeWeatherTargetWithExtras(
+                        id = 2,
+                        userHandle = userHandleManaged,
+                        description = "Showers",
+                        state = WeatherData.WeatherStateIcon.SHOWERS_RAIN.id,
+                        temperature = "62",
+                        useCelsius = true)
+        )
+        sessionListener.onTargetsAvailable(targets)
+        verify(keyguardUpdateMonitor).sendWeatherData(argThat { w ->
+            w.description == "Sunny" &&
+                    w.state == WeatherData.WeatherStateIcon.SUNNY &&
+                    w.temperature == 72 && !w.useCelsius
+        })
+    }
+
+    @Test
+    fun testSessionListener_ifDecouplingEnabled_weatherDataUpdates() {
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+        connectSession()
+
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeTarget(1, userHandlePrimary, isSensitive = true),
+                makeTarget(2, userHandlePrimary),
+                makeTarget(3, userHandleManaged),
+                makeWeatherTargetWithExtras(
+                        id = 4,
+                        userHandle = userHandlePrimary,
+                        description = "Flurries",
+                        state = WeatherData.WeatherStateIcon.FLURRIES.id,
+                        temperature = "0",
+                        useCelsius = true)
+        )
+
+        sessionListener.onTargetsAvailable(targets)
+
+        verify(keyguardUpdateMonitor).sendWeatherData(argThat { w ->
+            w.description == "Flurries" &&
+                    w.state == WeatherData.WeatherStateIcon.FLURRIES &&
+                    w.temperature == 0 && w.useCelsius
+        })
+    }
+
+    @Test
+    fun testSessionListener_ifDecouplingDisabled_weatherDataUpdates() {
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
+        connectSession()
+
+        clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT)
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeWeatherTargetWithExtras(
+                        id = 1,
+                        userHandle = userHandlePrimary,
+                        description = "Sunny",
+                        state = WeatherData.WeatherStateIcon.SUNNY.id,
+                        temperature = "32",
+                        useCelsius = false),
+                makeTarget(2, userHandlePrimary, isSensitive = true)
+        )
+
+        sessionListener.onTargetsAvailable(targets)
+
+        verify(keyguardUpdateMonitor).sendWeatherData(argThat { w ->
+            w.description == "Sunny" &&
+                    w.state == WeatherData.WeatherStateIcon.SUNNY &&
+                    w.temperature == 32 && !w.useCelsius
+        })
+    }
+
+    @Test
     fun testSettingsAreReloaded() {
         // GIVEN a connected session where the privacy settings later flip to false
         connectSession()
@@ -740,7 +937,7 @@
         return userInfo
     }
 
-    fun makeTarget(
+    private fun makeTarget(
         id: Int,
         userHandle: UserHandle,
         isSensitive: Boolean = false,
@@ -755,6 +952,38 @@
                 .build()
     }
 
+    private fun makeWeatherTargetWithExtras(
+            id: Int,
+            userHandle: UserHandle,
+            description: String?,
+            state: Int?,
+            temperature: String?,
+            useCelsius: Boolean?
+    ): SmartspaceTarget {
+        val mockWeatherBundle = mock(Bundle::class.java).apply {
+            `when`(getString(WeatherData.DESCRIPTION_KEY)).thenReturn(description)
+            if (state != null)
+                `when`(getInt(eq(WeatherData.STATE_KEY), any())).thenReturn(state)
+            `when`(getString(WeatherData.TEMPERATURE_KEY)).thenReturn(temperature)
+            `when`(containsKey(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius != null)
+            if (useCelsius != null)
+                `when`(getBoolean(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius)
+        }
+
+        val mockBaseAction = mock(SmartspaceAction::class.java)
+        `when`(mockBaseAction.extras).thenReturn(mockWeatherBundle)
+        return SmartspaceTarget.Builder(
+                "targetWithWeatherExtras$id",
+                ComponentName("testpackage", "testclass$id"),
+                userHandle)
+                .setSensitive(false)
+                .setFeatureType(SmartspaceTarget.FEATURE_WEATHER)
+                .setBaseAction(mockBaseAction)
+                .setExpiryTimeMillis(SMARTSPACE_EXPIRY_TIME)
+                .setCreationTimeMillis(SMARTSPACE_CREATION_TIME)
+                .build()
+    }
+
     private fun setAllowPrivateNotifications(user: UserHandle, value: Boolean) {
         `when`(secureSettings.getIntForUser(
                 eq(PRIVATE_LOCKSCREEN_SETTING),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt
new file mode 100644
index 0000000..89cc18cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RippleShaderTest : SysuiTestCase() {
+
+    private lateinit var rippleShader: RippleShader
+
+    @Before
+    fun setup() {
+        rippleShader = RippleShader()
+    }
+
+    @Test
+    fun setMaxSize_hasCorrectSizes() {
+        val expectedMaxWidth = 300f
+        val expectedMaxHeight = 500f
+
+        rippleShader.rippleSize.setMaxSize(expectedMaxWidth, expectedMaxHeight)
+
+        assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(2)
+        assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize)
+        val maxSize = rippleShader.rippleSize.sizes[1]
+        assertThat(maxSize.t).isEqualTo(1f)
+        assertThat(maxSize.width).isEqualTo(expectedMaxWidth)
+        assertThat(maxSize.height).isEqualTo(expectedMaxHeight)
+    }
+
+    @Test
+    fun setSizeAtProgresses_hasCorrectSizes() {
+        val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f)
+        val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f)
+        val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f)
+
+        rippleShader.rippleSize.setSizeAtProgresses(expectedSize0, expectedSize1, expectedSize2)
+
+        assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(3)
+        assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0)
+        assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1)
+        assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2)
+    }
+
+    @Test
+    fun setSizeAtProgresses_sizeListIsSortedByT() {
+        val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f)
+        val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f)
+        val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f)
+        val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f)
+        val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f)
+
+        // Add them in unsorted order
+        rippleShader.rippleSize.setSizeAtProgresses(
+            expectedSize0,
+            expectedSize3,
+            expectedSize2,
+            expectedSize4,
+            expectedSize1
+        )
+
+        assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(5)
+        assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0)
+        assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1)
+        assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2)
+        assertThat(rippleShader.rippleSize.sizes[3]).isEqualTo(expectedSize3)
+        assertThat(rippleShader.rippleSize.sizes[4]).isEqualTo(expectedSize4)
+    }
+
+    @Test
+    fun update_getsCorrectNextTargetSize() {
+        val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f)
+        val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f)
+        val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f)
+        val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f)
+        val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f)
+
+        rippleShader.rippleSize.setSizeAtProgresses(
+            expectedSize0,
+            expectedSize1,
+            expectedSize2,
+            expectedSize3,
+            expectedSize4
+        )
+
+        rippleShader.rippleSize.update(0.5f)
+        // Progress is between 0.4 and 0.8 (expectedSize3 and 4), so the index should be 3.
+        assertThat(rippleShader.rippleSize.currentSizeIndex).isEqualTo(3)
+    }
+
+    @Test
+    fun update_sizeListIsEmpty_setsInitialSize() {
+        assertThat(rippleShader.rippleSize.sizes).isEmpty()
+
+        rippleShader.rippleSize.update(0.3f)
+
+        assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(1)
+        assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
index 3374219..9cdce20 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [KeyguardRepository] */
@@ -44,8 +45,6 @@
     override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
     private val _keyguardPosition = MutableStateFlow(0f)
     override val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _onScreenTurnedOff = MutableStateFlow(false)
-    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
     override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
@@ -61,6 +60,8 @@
     override var lastAlternateBouncerVisibleTime: Long = 0L
     private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
     override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
+    private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
 
     override fun setPrimaryScrimmed(isScrimmed: Boolean) {
         _primaryBouncerScrimmed.value = isScrimmed
@@ -122,7 +123,7 @@
         _isBackButtonEnabled.value = isBackButtonEnabled
     }
 
-    override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
-        _onScreenTurnedOff.value = onScreenTurnedOff
+    override fun setSideFpsShowing(isShowing: Boolean) {
+        _sideFpsShowing.value = isShowing
     }
 }
diff --git a/services/Android.bp b/services/Android.bp
index f8097ec..6e6c553 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -195,6 +195,10 @@
         "manifest_services.xml",
     ],
 
+    required: [
+        "libukey2_jni_shared",
+    ],
+
     // Uncomment to enable output of certain warnings (deprecated, unchecked)
     //javacflags: ["-Xlint"],
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 328b971..cde820a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4577,6 +4577,17 @@
         return false;
     }
 
+    /**
+     * Called when always on magnification feature flag flips to check if the feature should be
+     * enabled for current user state.
+     */
+    public void updateAlwaysOnMagnification() {
+        synchronized (mLock) {
+            readAlwaysOnMagnificationLocked(getCurrentUserState());
+        }
+    }
+
+    @GuardedBy("mLock")
     boolean readAlwaysOnMagnificationLocked(AccessibilityUserState userState) {
         final boolean isSettingsAlwaysOnEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index e6abc4c..eba9230 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -335,7 +335,7 @@
         KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0,
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
                 InputDevice.SOURCE_KEYBOARD, null);
-        InputManager.getInstance()
+        mContext.getSystemService(InputManager.class)
                 .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
         event.recycle();
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
index ed45e7b..16d2e6b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
@@ -16,10 +16,13 @@
 
 package com.android.server.accessibility.magnification;
 
+import android.annotation.NonNull;
 import android.provider.DeviceConfig;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.concurrent.Executor;
+
 /**
  * Encapsulates the feature flags for always on magnification. {@see DeviceConfig}
  *
@@ -50,4 +53,39 @@
                 Boolean.toString(isEnabled),
                 /* makeDefault= */ false);
     }
+
+    /**
+     * Adds a listener for when the feature flag changes.
+     *
+     * <p>{@see DeviceConfig#addOnPropertiesChangedListener(
+     * String, Executor, DeviceConfig.OnPropertiesChangedListener)}
+     */
+    @NonNull
+    public static DeviceConfig.OnPropertiesChangedListener addOnChangedListener(
+            @NonNull Executor executor, @NonNull Runnable listener) {
+        DeviceConfig.OnPropertiesChangedListener onChangedListener =
+                properties -> {
+                    if (properties.getKeyset().contains(
+                            FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION)) {
+                        listener.run();
+                    }
+                };
+        DeviceConfig.addOnPropertiesChangedListener(
+                NAMESPACE,
+                executor,
+                onChangedListener);
+
+        return onChangedListener;
+    }
+
+    /**
+     * Remove a listener for when the feature flag changes.
+     *
+     * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor,
+     * DeviceConfig.OnPropertiesChangedListener)}
+     */
+    public static void removeOnChangedListener(
+            @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) {
+        DeviceConfig.removeOnPropertiesChangedListener(onChangedListener);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index a6e6bd7..4753a54 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -48,6 +48,7 @@
 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.wm.WindowManagerInternal;
@@ -149,6 +150,9 @@
                 .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
         mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
                 FEATURE_WINDOW_MAGNIFICATION);
+
+        AlwaysOnMagnificationFeatureFlag.addOnChangedListener(
+                ConcurrentUtils.DIRECT_EXECUTOR, mAms::updateAlwaysOnMagnification);
     }
 
     @VisibleForTesting
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index cdeb2dc..a248d9e5 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -24,4 +24,7 @@
         "app-compat-annotations",
         "services.core",
     ],
+    static_libs: [
+        "ukey2_jni",
+    ],
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0f2ba35..a35cae9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -726,6 +726,11 @@
         }
 
         @Override
+        public void enableSecureTransport(boolean enabled) {
+            mTransportManager.enableSecureTransport(enabled);
+        }
+
+        @Override
         public void notifyDeviceAppeared(int associationId) {
             if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
 
diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
new file mode 100644
index 0000000..adaee75
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.securechannel;
+
+import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
+import static android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE;
+import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Bundle;
+import android.security.attestationverification.AttestationProfile;
+import android.security.attestationverification.AttestationVerificationManager;
+import android.security.attestationverification.VerificationToken;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+
+/**
+ * Helper class to perform attestation verification synchronously.
+ */
+class AttestationVerifier {
+    private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds
+    private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
+
+    private final Context mContext;
+
+    AttestationVerifier(Context context) {
+        this.mContext = context;
+    }
+
+    /**
+     * Synchronously verify remote attestation as a suitable peer device on current thread.
+     *
+     * The peer device must be owned by the Android system and be protected with appropriate
+     * public key that this device can verify as attestation challenge.
+     *
+     * @param remoteAttestation the full certificate chain containing attestation extension.
+     * @param attestationChallenge attestation challenge for authentication.
+     * @return true if attestation is successfully verified; false otherwise.
+     */
+    @NonNull
+    public int verifyAttestation(
+            @NonNull byte[] remoteAttestation,
+            @NonNull byte[] attestationChallenge
+    ) throws SecureChannelException {
+        Bundle requirements = new Bundle();
+        requirements.putByteArray(PARAM_CHALLENGE, attestationChallenge);
+        requirements.putBoolean(PARAM_OWNED_BY_SYSTEM, true); // Custom parameter for CDM
+
+        // Synchronously execute attestation verification.
+        AtomicInteger verificationResult = new AtomicInteger(0);
+        CountDownLatch verificationFinished = new CountDownLatch(1);
+        BiConsumer<Integer, VerificationToken> onVerificationResult = (result, token) -> {
+            verificationResult.set(result);
+            verificationFinished.countDown();
+        };
+
+        mContext.getSystemService(AttestationVerificationManager.class).verifyAttestation(
+                new AttestationProfile(PROFILE_PEER_DEVICE),
+                /* localBindingType */ TYPE_CHALLENGE,
+                requirements,
+                remoteAttestation,
+                Runnable::run,
+                onVerificationResult
+        );
+
+        boolean finished;
+        try {
+            finished = verificationFinished.await(
+                    ATTESTATION_VERIFICATION_TIMEOUT_SECONDS,
+                    TimeUnit.SECONDS
+            );
+        } catch (InterruptedException e) {
+            throw new SecureChannelException("Attestation verification was interrupted", e);
+        }
+
+        if (!finished) {
+            throw new SecureChannelException("Attestation verification timed out.");
+        }
+
+        return verificationResult.get();
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/securechannel/KeyStoreUtils.java b/services/companion/java/com/android/server/companion/securechannel/KeyStoreUtils.java
new file mode 100644
index 0000000..18ebec4
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/securechannel/KeyStoreUtils.java
@@ -0,0 +1,130 @@
+/*
+ * 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.companion.securechannel;
+
+import static android.security.keystore.KeyProperties.DIGEST_SHA256;
+import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
+import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
+import static android.security.keystore.KeyProperties.PURPOSE_VERIFY;
+
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore2.AndroidKeyStoreSpi;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+
+/**
+ * Utility class to help generate, store, and access key-pair for the secure channel. Uses
+ * Android Keystore.
+ */
+final class KeyStoreUtils {
+    private static final String TAG = "CDM_SecureChannelKeyStore";
+    private static final String ANDROID_KEYSTORE = AndroidKeyStoreSpi.NAME;
+
+    private KeyStoreUtils() {}
+
+    /**
+     * Load Android keystore to be used by the secure channel.
+     *
+     * @return loaded keystore instance
+     */
+    static KeyStore loadKeyStore() throws GeneralSecurityException {
+        KeyStore androidKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
+
+        try {
+            androidKeyStore.load(null);
+        } catch (IOException e) {
+            // Should not happen
+            throw new KeyStoreException("Failed to load Android Keystore.", e);
+        }
+
+        return androidKeyStore;
+    }
+
+    /**
+     * Fetch the certificate chain encoded as byte array in the form of concatenated
+     * X509 certificates.
+     *
+     * @param alias unique alias for the key-pair entry
+     * @return a single byte-array containing the entire certificate chain
+     */
+    static byte[] getEncodedCertificateChain(String alias) throws GeneralSecurityException {
+        KeyStore ks = loadKeyStore();
+
+        Certificate[] certificateChain = ks.getCertificateChain(alias);
+
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        for (Certificate certificate : certificateChain) {
+            buffer.writeBytes(certificate.getEncoded());
+        }
+        return buffer.toByteArray();
+    }
+
+    /**
+     * Generate a new attestation key-pair.
+     *
+     * @param alias unique alias for the key-pair entry
+     * @param attestationChallenge challenge value to check against for authentication
+     */
+    static void generateAttestationKeyPair(String alias, byte[] attestationChallenge)
+            throws GeneralSecurityException {
+        KeyGenParameterSpec parameterSpec =
+                new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN | PURPOSE_VERIFY)
+                        .setAttestationChallenge(attestationChallenge)
+                        .setDigests(DIGEST_SHA256)
+                        .build();
+
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
+                /* algorithm */ KEY_ALGORITHM_EC,
+                /* provider */ ANDROID_KEYSTORE);
+        keyPairGenerator.initialize(parameterSpec);
+        keyPairGenerator.generateKeyPair();
+    }
+
+    /**
+     * Check if alias exists.
+     *
+     * @param alias unique alias for the key-pair entry
+     * @return true if given alias already exists in the keystore
+     */
+    static boolean aliasExists(String alias) {
+        try {
+            KeyStore ks = loadKeyStore();
+            return ks.containsAlias(alias);
+        } catch (GeneralSecurityException e) {
+            return false;
+        }
+
+    }
+
+    static void cleanUp(String alias) {
+        try {
+            KeyStore ks = loadKeyStore();
+
+            if (ks.containsAlias(alias)) {
+                ks.deleteEntry(alias);
+            }
+        } catch (Exception ignored) {
+            // Do nothing;
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
new file mode 100644
index 0000000..13dba84
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.securechannel;
+
+import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Build;
+import android.util.Slog;
+
+import com.google.security.cryptauth.lib.securegcm.BadHandleException;
+import com.google.security.cryptauth.lib.securegcm.CryptoException;
+import com.google.security.cryptauth.lib.securegcm.D2DConnectionContextV1;
+import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext;
+import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext.Role;
+import com.google.security.cryptauth.lib.securegcm.DefaultUkey2Logger;
+import com.google.security.cryptauth.lib.securegcm.HandshakeException;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * Data stream channel that establishes secure connection between two peer devices.
+ */
+public class SecureChannel {
+    private static final String TAG = "CDM_SecureChannel";
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+
+    private static final int VERSION = 1;
+    private static final int HEADER_LENGTH = 6;
+
+    private static final String HANDSHAKE_PROTOCOL = "AES_256_CBC-HMAC_SHA256";
+
+    private final InputStream mInput;
+    private final OutputStream mOutput;
+    private final Callback mCallback;
+    private final byte[] mPreSharedKey;
+    private final AttestationVerifier mVerifier;
+
+    private volatile boolean mStopped;
+    private boolean mInProgress;
+
+    private Role mRole;
+    private D2DHandshakeContext mHandshakeContext;
+    private D2DConnectionContextV1 mConnectionContext;
+
+    private String mAlias;
+    private int mVerificationResult;
+
+
+    /**
+     * Create a new secure channel object. This secure channel allows secure messages to be
+     * exchanged with unattested devices. The pre-shared key must have been distributed to both
+     * participants of the channel in a secure way previously.
+     *
+     * @param in input stream from which data is received
+     * @param out output stream from which data is sent out
+     * @param callback subscription to received messages from the channel
+     * @param preSharedKey pre-shared key to authenticate unattested participant
+     */
+    public SecureChannel(
+            @NonNull final InputStream in,
+            @NonNull final OutputStream out,
+            @NonNull Callback callback,
+            @NonNull byte[] preSharedKey
+    ) {
+        this(in, out, callback, preSharedKey, null);
+    }
+
+    /**
+     * Create a new secure channel object. This secure channel allows secure messages to be
+     * exchanged with Android devices that were authenticated and verified with an attestation key.
+     *
+     * @param in input stream from which data is received
+     * @param out output stream from which data is sent out
+     * @param callback subscription to received messages from the channel
+     * @param context context for fetching the Attestation Verifier Framework system service
+     */
+    public SecureChannel(
+            @NonNull final InputStream in,
+            @NonNull final OutputStream out,
+            @NonNull Callback callback,
+            @NonNull Context context
+    ) {
+        this(in, out, callback, null, new AttestationVerifier(context));
+    }
+
+    private SecureChannel(
+            final InputStream in,
+            final OutputStream out,
+            Callback callback,
+            byte[] preSharedKey,
+            AttestationVerifier verifier
+    ) {
+        this.mInput = in;
+        this.mOutput = out;
+        this.mCallback = callback;
+        this.mPreSharedKey = preSharedKey;
+        this.mVerifier = verifier;
+    }
+
+    /**
+     * Start listening for incoming messages.
+     */
+    public void start() {
+        new Thread(() -> {
+            try {
+                // 1. Wait for the next handshake message and process it.
+                exchangeHandshake();
+
+                // 2. Authenticate remote actor via attestation or pre-shared key.
+                exchangeAuthentication();
+
+                // 3. Notify secure channel is ready.
+                mInProgress = false;
+                mCallback.onSecureConnection();
+
+                // Listen for secure messages.
+                while (!mStopped) {
+                    receiveSecureMessage();
+                }
+            } catch (Exception e) {
+                if (mStopped) {
+                    return;
+                }
+                // TODO: Handle different types errors.
+
+                Slog.e(TAG, "Secure channel encountered an error.", e);
+                stop();
+                mCallback.onError(e);
+            }
+        }).start();
+    }
+
+    /**
+     * Stop listening to incoming messages and close the channel.
+     */
+    public void stop() {
+        if (DEBUG) {
+            Slog.d(TAG, "Stopping secure channel.");
+        }
+        mStopped = true;
+        mInProgress = false;
+
+        IoUtils.closeQuietly(mInput);
+        IoUtils.closeQuietly(mOutput);
+        KeyStoreUtils.cleanUp(mAlias);
+    }
+
+    /**
+     * Start exchanging handshakes to create a secure layer asynchronously. When the handshake is
+     * completed successfully, then the {@link Callback#onSecureConnection()} will trigger. Any
+     * error that occurs during the handshake will be passed by {@link Callback#onError(Throwable)}.
+     *
+     * This method must only be called from one of the two participants.
+     */
+    public void establishSecureConnection() throws IOException, SecureChannelException {
+        if (isSecured()) {
+            Slog.d(TAG, "Channel is already secure.");
+            return;
+        }
+        if (mInProgress) {
+            Slog.w(TAG, "Channel has already started establishing secure connection.");
+            return;
+        }
+
+        try {
+            initiateHandshake();
+            mInProgress = true;
+        } catch (BadHandleException e) {
+            throw new SecureChannelException("Failed to initiate handshake protocol.", e);
+        }
+    }
+
+    /**
+     * Send an encrypted, authenticated message via this channel.
+     *
+     * @param data data to be sent to the other side.
+     * @throws IOException if the output stream fails to write given data.
+     */
+    public void sendSecureMessage(byte[] data) throws IOException {
+        if (!isSecured()) {
+            Slog.d(TAG, "Cannot send a message without a secure connection.");
+            throw new IllegalStateException("Channel is not secured yet.");
+        }
+
+        // Encrypt constructed message
+        try {
+            sendMessage(MessageType.SECURE_MESSAGE, data);
+        } catch (BadHandleException e) {
+            throw new SecureChannelException("Failed to encrypt data.", e);
+        }
+    }
+
+    private void receiveSecureMessage() throws IOException, CryptoException {
+        // Check if channel is secured. Trigger error callback. Let user handle it.
+        if (!isSecured()) {
+            Slog.d(TAG, "Received a message without a secure connection. "
+                    + "Message will be ignored.");
+            mCallback.onError(new IllegalStateException("Connection is not secure."));
+            return;
+        }
+
+        try {
+            byte[] receivedMessage = readMessage(MessageType.SECURE_MESSAGE);
+            mCallback.onSecureMessageReceived(receivedMessage);
+        } catch (SecureChannelException e) {
+            Slog.w(TAG, "Ignoring received message.", e);
+        }
+    }
+
+    private byte[] readMessage(MessageType expected)
+            throws IOException, SecureChannelException, CryptoException {
+        if (DEBUG) {
+            if (isSecured()) {
+                Slog.d(TAG, "Waiting to receive next secure message.");
+            } else {
+                Slog.d(TAG, "Waiting to receive next message.");
+            }
+        }
+
+        // TODO: Handle message timeout
+
+        // Header is _not_ encrypted, but will be covered by MAC
+        final byte[] headerBytes = new byte[HEADER_LENGTH];
+        Streams.readFully(mInput, headerBytes);
+        final ByteBuffer header = ByteBuffer.wrap(headerBytes);
+        final int version = header.getInt();
+        final short type = header.getShort();
+
+        if (version != VERSION) {
+            Streams.skipByReading(mInput, Long.MAX_VALUE);
+            throw new SecureChannelException("Secure channel version mismatch. "
+                    + "Currently on version " + VERSION + ". Skipping rest of data.");
+        }
+
+        if (type != expected.mValue) {
+            Streams.skipByReading(mInput, Long.MAX_VALUE);
+            throw new SecureChannelException("Unexpected message type. Expected " + expected.name()
+                    + "; Found " + MessageType.from(type).name() + ". Skipping rest of data.");
+        }
+
+        // Length of attached data is prepended as plaintext
+        final byte[] lengthBytes = new byte[4];
+        Streams.readFully(mInput, lengthBytes);
+        final int length = ByteBuffer.wrap(lengthBytes).getInt();
+
+        // Read data based on the length
+        final byte[] data;
+        try {
+            data = new byte[length];
+        } catch (OutOfMemoryError error) {
+            throw new SecureChannelException("Payload is too large.", error);
+        }
+
+        Streams.readFully(mInput, data);
+        if (!MessageType.shouldEncrypt(expected)) {
+            return data;
+        }
+
+        return mConnectionContext.decodeMessageFromPeer(data, headerBytes);
+    }
+
+    private void sendMessage(MessageType messageType, byte[] payload)
+            throws IOException, BadHandleException {
+        synchronized (mOutput) {
+            byte[] header = ByteBuffer.allocate(HEADER_LENGTH)
+                    .putInt(VERSION)
+                    .putShort(messageType.mValue)
+                    .array();
+            byte[] data = MessageType.shouldEncrypt(messageType)
+                    ? mConnectionContext.encodeMessageToPeer(payload, header)
+                    : payload;
+            mOutput.write(header);
+            mOutput.write(ByteBuffer.allocate(4)
+                    .putInt(data.length)
+                    .array());
+            mOutput.write(data);
+            mOutput.flush();
+        }
+    }
+
+    private void initiateHandshake() throws IOException, BadHandleException {
+        if (mConnectionContext != null) {
+            Slog.d(TAG, "Ukey2 handshake is already completed.");
+            return;
+        }
+
+        mRole = Role.Initiator;
+        mHandshakeContext = D2DHandshakeContext.forInitiator(DefaultUkey2Logger.INSTANCE);
+
+        // Send Client Init
+        if (DEBUG) {
+            Slog.d(TAG, "Sending Ukey2 Client Init message");
+        }
+        sendMessage(MessageType.HANDSHAKE_INIT, mHandshakeContext.getNextHandshakeMessage());
+    }
+
+    private void exchangeHandshake()
+            throws IOException, HandshakeException, BadHandleException, CryptoException {
+        if (mConnectionContext != null) {
+            Slog.d(TAG, "Ukey2 handshake is already completed.");
+            return;
+        }
+
+        // Waiting for message
+        byte[] handshakeMessage = readMessage(MessageType.HANDSHAKE_INIT);
+
+        if (mHandshakeContext == null) { // Server-side logic
+            mRole = Role.Responder;
+            mHandshakeContext = D2DHandshakeContext.forResponder(DefaultUkey2Logger.INSTANCE);
+
+            // Receive Client Init
+            if (DEBUG) {
+                Slog.d(TAG, "Receiving Ukey2 Client Init message");
+            }
+            mHandshakeContext.parseHandshakeMessage(handshakeMessage);
+
+            // Send Server Init
+            if (DEBUG) {
+                Slog.d(TAG, "Sending Ukey2 Server Init message");
+            }
+            sendMessage(MessageType.HANDSHAKE_INIT, mHandshakeContext.getNextHandshakeMessage());
+
+            // Receive Client Finish
+            if (DEBUG) {
+                Slog.d(TAG, "Receiving Ukey2 Client Finish message");
+            }
+            mHandshakeContext.parseHandshakeMessage(readMessage(MessageType.HANDSHAKE_FINISH));
+        } else { // Client-side logic
+
+            // Receive Server Init
+            if (DEBUG) {
+                Slog.d(TAG, "Receiving Ukey2 Server Init message");
+            }
+            mHandshakeContext.parseHandshakeMessage(handshakeMessage);
+
+            // Send Client Finish
+            if (DEBUG) {
+                Slog.d(TAG, "Sending Ukey2 Client Finish message");
+            }
+            sendMessage(MessageType.HANDSHAKE_FINISH, mHandshakeContext.getNextHandshakeMessage());
+        }
+
+        // Convert secrets to connection context
+        if (mHandshakeContext.isHandshakeComplete()) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ukey2 Handshake completed successfully");
+            }
+            mConnectionContext = mHandshakeContext.toConnectionContext();
+        } else {
+            Slog.e(TAG, "Failed to complete Ukey2 Handshake");
+            throw new IllegalStateException("Ukey2 Handshake did not complete as expected.");
+        }
+    }
+
+    private void exchangeAuthentication()
+            throws IOException, GeneralSecurityException, BadHandleException, CryptoException {
+        if (mVerifier == null) {
+            exchangePreSharedKey();
+        } else {
+            exchangeAttestation();
+        }
+    }
+
+    private void exchangePreSharedKey()
+            throws IOException, GeneralSecurityException, BadHandleException, CryptoException {
+
+        // Exchange hashed pre-shared keys
+        if (DEBUG) {
+            Slog.d(TAG, "Exchanging pre-shared keys.");
+        }
+        sendMessage(MessageType.PRE_SHARED_KEY, constructToken(mRole, mPreSharedKey));
+        byte[] receivedAuthToken = readMessage(MessageType.PRE_SHARED_KEY);
+        byte[] expectedAuthToken = constructToken(mRole == Role.Initiator
+                ? Role.Responder
+                : Role.Initiator,
+                mPreSharedKey);
+        boolean authenticated = Arrays.equals(receivedAuthToken, expectedAuthToken);
+
+        if (!authenticated) {
+            throw new SecureChannelException("Failed to verify the hash of pre-shared key.");
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "The pre-shared key was successfully authenticated.");
+        }
+    }
+
+    private void exchangeAttestation()
+            throws IOException, GeneralSecurityException, BadHandleException, CryptoException {
+        if (mVerificationResult == RESULT_SUCCESS) {
+            Slog.d(TAG, "Remote attestation was already verified.");
+            return;
+        }
+
+        // Send local attestation
+        if (DEBUG) {
+            Slog.d(TAG, "Exchanging device attestation.");
+        }
+        if (mAlias == null) {
+            mAlias = generateAlias();
+        }
+        byte[] localChallenge = constructToken(mRole, mConnectionContext.getSessionUnique());
+        KeyStoreUtils.generateAttestationKeyPair(mAlias, localChallenge);
+        byte[] localAttestation = KeyStoreUtils.getEncodedCertificateChain(mAlias);
+        sendMessage(MessageType.ATTESTATION, localAttestation);
+        byte[] remoteAttestation = readMessage(MessageType.ATTESTATION);
+
+        // Verifying remote attestation with public key local binding param
+        byte[] expectedChallenge = constructToken(mRole == Role.Initiator
+                ? Role.Responder
+                : Role.Initiator,
+                mConnectionContext.getSessionUnique());
+        mVerificationResult = mVerifier.verifyAttestation(remoteAttestation, expectedChallenge);
+
+        // Exchange attestation verification result and finish
+        byte[] verificationResult = ByteBuffer.allocate(4)
+                .putInt(mVerificationResult)
+                .array();
+        sendMessage(MessageType.AVF_RESULT, verificationResult);
+        byte[] remoteVerificationResult = readMessage(MessageType.AVF_RESULT);
+
+        if (ByteBuffer.wrap(remoteVerificationResult).getInt() != RESULT_SUCCESS) {
+            throw new SecureChannelException("Remote device failed to verify local attestation.");
+        }
+
+        if (mVerificationResult != RESULT_SUCCESS) {
+            throw new SecureChannelException("Failed to verify remote attestation.");
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "Remote attestation was successfully verified.");
+        }
+    }
+
+    private boolean isSecured() {
+        if (mConnectionContext == null) {
+            return false;
+        }
+        return mVerifier == null || mVerificationResult == RESULT_SUCCESS;
+    }
+
+    private byte[] constructToken(D2DHandshakeContext.Role role, byte[] authValue)
+            throws GeneralSecurityException {
+        MessageDigest hash = MessageDigest.getInstance("SHA-256");
+        byte[] roleUtf8 = role.name().getBytes(StandardCharsets.UTF_8);
+        int tokenLength = roleUtf8.length + authValue.length;
+        return hash.digest(ByteBuffer.allocate(tokenLength)
+                .put(roleUtf8)
+                .put(authValue)
+                .array());
+    }
+
+    private String generateAlias() {
+        String alias;
+        do {
+            alias = "secure-channel-" + UUID.randomUUID();
+        } while (KeyStoreUtils.aliasExists(alias));
+        return alias;
+    }
+
+    private enum MessageType {
+        HANDSHAKE_INIT(0x4849),   // HI
+        HANDSHAKE_FINISH(0x4846), // HF
+        PRE_SHARED_KEY(0x504b),   // PK
+        ATTESTATION(0x4154),      // AT
+        AVF_RESULT(0x5652),       // VR
+        SECURE_MESSAGE(0x534d),   // SM
+        UNKNOWN(0);               // X
+
+        private final short mValue;
+
+        MessageType(int value) {
+            this.mValue = (short) value;
+        }
+
+        static MessageType from(short value) {
+            for (MessageType messageType : values()) {
+                if (value == messageType.mValue) {
+                    return messageType;
+                }
+            }
+            return UNKNOWN;
+        }
+
+        // Encrypt every message besides Ukey2 handshake messages
+        private static boolean shouldEncrypt(MessageType type) {
+            return type != HANDSHAKE_INIT && type != HANDSHAKE_FINISH;
+        }
+    }
+
+    /**
+     * Callback that passes securely received message to the subscribed user.
+     */
+    public interface Callback {
+        /**
+         * Triggered after {@link SecureChannel#establishSecureConnection()} finishes exchanging
+         * every required handshakes to fully establish a secure connection.
+         */
+        void onSecureConnection();
+
+        /**
+         * Callback that passes securely received and decrypted data to the subscribed user.
+         *
+         * @param data securely received plaintext data.
+         */
+        void onSecureMessageReceived(byte[] data);
+
+        /**
+         * Callback that passes error that occurred during handshakes or while listening to
+         * messages in the secure channel.
+         *
+         * @param error
+         */
+        void onError(Throwable error);
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannelException.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannelException.java
new file mode 100644
index 0000000..68db97e
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannelException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.securechannel;
+
+/**
+ * Catch-all exception for any error in the secure channel.
+ */
+public class SecureChannelException extends RuntimeException {
+    /**
+     *
+     * @param message
+     */
+    public SecureChannelException(String message) {
+        super(message);
+    }
+
+    public SecureChannelException(String message, Throwable t) {
+        super(message, t);
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 6db99a0..494c5a6 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
+import com.android.server.companion.securechannel.SecureChannel;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -43,6 +44,8 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -54,8 +57,6 @@
     private static final boolean DEBUG = true;
 
     private static final int HEADER_LENGTH = 12;
-    // TODO: refactor message processing to use streams to remove this limit
-    private static final int MAX_PAYLOAD_LENGTH = 1_000_000;
 
     private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
     private static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
@@ -63,6 +64,8 @@
     private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
     private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
 
+    private boolean mSecureTransportEnabled = true;
+
     private static boolean isRequest(int message) {
         return (message & 0xFF000000) == 0x63000000;
     }
@@ -122,7 +125,13 @@
                 detachSystemDataTransport(packageName, userId, associationId);
             }
 
-            final Transport transport = new Transport(associationId, fd);
+            final Transport transport;
+            if (isSecureTransportEnabled(associationId)) {
+                transport = new SecureTransport(associationId, fd);
+            } else {
+                transport = new RawTransport(associationId, fd);
+            }
+
             transport.start();
             mTransports.put(associationId, transport);
         }
@@ -142,61 +151,65 @@
     public Future<?> requestPermissionRestore(int associationId, byte[] data) {
         synchronized (mTransports) {
             final Transport transport = mTransports.get(associationId);
-            if (transport != null) {
-                return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
-            } else {
+            if (transport == null) {
                 return CompletableFuture.failedFuture(new IOException("Missing transport"));
             }
+
+            return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
         }
     }
 
-    private class Transport {
-        private final int mAssociationId;
+    /**
+     * @hide
+     */
+    public void enableSecureTransport(boolean enabled) {
+        this.mSecureTransportEnabled = enabled;
+    }
 
-        private final InputStream mRemoteIn;
-        private final OutputStream mRemoteOut;
+    private boolean isSecureTransportEnabled(int associationId) {
+        boolean enabled = !Build.IS_DEBUGGABLE || mSecureTransportEnabled;
 
-        private final AtomicInteger mNextSequence = new AtomicInteger();
+        // TODO: version comparison logic
+        return enabled;
+    }
+
+    // TODO: Make Transport inner classes into standalone classes.
+    private abstract class Transport {
+        protected final int mAssociationId;
+        protected final InputStream mRemoteIn;
+        protected final OutputStream mRemoteOut;
 
         @GuardedBy("mPendingRequests")
-        private final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>();
+        protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests =
+                new SparseArray<>();
+        protected final AtomicInteger mNextSequence = new AtomicInteger();
 
-        private volatile boolean mStopped;
-
-        public Transport(int associationId, ParcelFileDescriptor fd) {
-            mAssociationId = associationId;
-            mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
-            mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+        Transport(int associationId, ParcelFileDescriptor fd) {
+            this(associationId,
+                    new ParcelFileDescriptor.AutoCloseInputStream(fd),
+                    new ParcelFileDescriptor.AutoCloseOutputStream(fd));
         }
 
-        public void start() {
-            new Thread(() -> {
-                try {
-                    while (!mStopped) {
-                        receiveMessage();
-                    }
-                } catch (IOException e) {
-                    if (!mStopped) {
-                        Slog.w(TAG, "Trouble during transport", e);
-                        stop();
-                    }
-                }
-            }).start();
+        Transport(int associationId, InputStream in, OutputStream out) {
+            this.mAssociationId = associationId;
+            this.mRemoteIn = in;
+            this.mRemoteOut = out;
         }
 
-        public void stop() {
-            mStopped = true;
+        public abstract void start();
+        public abstract void stop();
 
-            IoUtils.closeQuietly(mRemoteIn);
-            IoUtils.closeQuietly(mRemoteOut);
-        }
+        protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
+                throws IOException;
 
         public Future<byte[]> requestForResponse(int message, byte[] data) {
+            if (DEBUG) Slog.d(TAG, "Requesting for response");
             final int sequence = mNextSequence.incrementAndGet();
             final CompletableFuture<byte[]> pending = new CompletableFuture<>();
             synchronized (mPendingRequests) {
                 mPendingRequests.put(sequence, pending);
             }
+
             try {
                 sendMessage(message, sequence, data);
             } catch (IOException e) {
@@ -205,58 +218,24 @@
                 }
                 pending.completeExceptionally(e);
             }
+
             return pending;
         }
 
-        private void sendMessage(int message, int sequence, @NonNull byte[] data)
+        protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
                 throws IOException {
             if (DEBUG) {
-                Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
-                        + " sequence " + sequence + " length " + data.length
-                        + " to association " + mAssociationId);
-            }
-
-            synchronized (mRemoteOut) {
-                final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH)
-                        .putInt(message)
-                        .putInt(sequence)
-                        .putInt(data.length);
-                mRemoteOut.write(header.array());
-                mRemoteOut.write(data);
-                mRemoteOut.flush();
-            }
-        }
-
-        private void receiveMessage() throws IOException {
-            if (DEBUG) {
-                Slog.d(TAG, "Waiting for next message...");
-            }
-
-            final byte[] headerBytes = new byte[HEADER_LENGTH];
-            Streams.readFully(mRemoteIn, headerBytes);
-            final ByteBuffer header = ByteBuffer.wrap(headerBytes);
-            final int message = header.getInt();
-            final int sequence = header.getInt();
-            final int length = header.getInt();
-
-            if (DEBUG) {
                 Slog.d(TAG, "Received message 0x" + Integer.toHexString(message)
-                        + " sequence " + sequence + " length " + length
+                        + " sequence " + sequence + " length " + data.length
                         + " from association " + mAssociationId);
             }
-            if (length > MAX_PAYLOAD_LENGTH) {
-                Slog.w(TAG, "Ignoring message 0x" + Integer.toHexString(message)
-                        + " sequence " + sequence + " length " + length
-                        + " from association " + mAssociationId + " beyond maximum length");
-                Streams.skipByReading(mRemoteIn, length);
-                return;
-            }
-
-            final byte[] data = new byte[length];
-            Streams.readFully(mRemoteIn, data);
 
             if (isRequest(message)) {
-                processRequest(message, sequence, data);
+                try {
+                    processRequest(message, sequence, data);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to respond to 0x" + Integer.toHexString(message), e);
+                }
             } else if (isResponse(message)) {
                 processResponse(message, sequence, data);
             } else {
@@ -320,4 +299,169 @@
             }
         }
     }
+
+    private class RawTransport extends Transport {
+        private volatile boolean mStopped;
+
+        RawTransport(int associationId, ParcelFileDescriptor fd) {
+            super(associationId, fd);
+        }
+
+        @Override
+        public void start() {
+            new Thread(() -> {
+                try {
+                    while (!mStopped) {
+                        receiveMessage();
+                    }
+                } catch (IOException e) {
+                    if (!mStopped) {
+                        Slog.w(TAG, "Trouble during transport", e);
+                        stop();
+                    }
+                }
+            }).start();
+        }
+
+        @Override
+        public void stop() {
+            mStopped = true;
+
+            IoUtils.closeQuietly(mRemoteIn);
+            IoUtils.closeQuietly(mRemoteOut);
+        }
+
+        @Override
+        protected void sendMessage(int message, int sequence, @NonNull byte[] data)
+                throws IOException {
+            if (DEBUG) {
+                Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+                        + " sequence " + sequence + " length " + data.length
+                        + " to association " + mAssociationId);
+            }
+
+            synchronized (mRemoteOut) {
+                final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH)
+                        .putInt(message)
+                        .putInt(sequence)
+                        .putInt(data.length);
+                mRemoteOut.write(header.array());
+                mRemoteOut.write(data);
+                mRemoteOut.flush();
+            }
+        }
+
+        private void receiveMessage() throws IOException {
+            final byte[] headerBytes = new byte[HEADER_LENGTH];
+            Streams.readFully(mRemoteIn, headerBytes);
+            final ByteBuffer header = ByteBuffer.wrap(headerBytes);
+            final int message = header.getInt();
+            final int sequence = header.getInt();
+            final int length = header.getInt();
+            final byte[] data = new byte[length];
+            Streams.readFully(mRemoteIn, data);
+
+            handleMessage(message, sequence, data);
+        }
+    }
+
+    private class SecureTransport extends Transport implements SecureChannel.Callback {
+        private final SecureChannel mSecureChannel;
+
+        private volatile boolean mShouldProcessRequests = false;
+
+        private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
+
+        SecureTransport(int associationId, ParcelFileDescriptor fd) {
+            super(associationId, fd);
+            mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, mContext);
+        }
+
+        @Override
+        public void start() {
+            mSecureChannel.start();
+        }
+
+        @Override
+        public void stop() {
+            mSecureChannel.stop();
+            mShouldProcessRequests = false;
+        }
+
+        @Override
+        public Future<byte[]> requestForResponse(int message, byte[] data) {
+            // Check if channel is secured and start securing
+            if (!mShouldProcessRequests) {
+                Slog.d(TAG, "Establishing secure connection.");
+                try {
+                    mSecureChannel.establishSecureConnection();
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
+                    onError(e);
+                }
+            }
+
+            return super.requestForResponse(message, data);
+        }
+
+        @Override
+        protected void sendMessage(int message, int sequence, @NonNull byte[] data)
+                throws IOException {
+            if (DEBUG) {
+                Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message)
+                        + " sequence " + sequence + " length " + data.length
+                        + " to association " + mAssociationId);
+            }
+
+            // Queue up a message to send
+            mRequestQueue.add(ByteBuffer.allocate(HEADER_LENGTH + data.length)
+                    .putInt(message)
+                    .putInt(sequence)
+                    .putInt(data.length)
+                    .put(data)
+                    .array());
+        }
+
+        @Override
+        public void onSecureConnection() {
+            mShouldProcessRequests = true;
+            Slog.d(TAG, "Secure connection established.");
+
+            // TODO: find a better way to handle incoming requests than a dedicated thread.
+            new Thread(() -> {
+                try {
+                    while (mShouldProcessRequests) {
+                        byte[] request = mRequestQueue.poll();
+                        if (request != null) {
+                            mSecureChannel.sendSecureMessage(request);
+                        }
+                    }
+                } catch (IOException e) {
+                    onError(e);
+                }
+            }).start();
+        }
+
+        @Override
+        public void onSecureMessageReceived(byte[] data) {
+            final ByteBuffer payload = ByteBuffer.wrap(data);
+            final int message = payload.getInt();
+            final int sequence = payload.getInt();
+            final int length = payload.getInt();
+            final byte[] content = new byte[length];
+            payload.get(content);
+
+            try {
+                handleMessage(message, sequence, content);
+            } catch (IOException error) {
+                onError(error);
+            }
+        }
+
+        @Override
+        public void onError(Throwable error) {
+            mShouldProcessRequests = false;
+            Slog.e(TAG, error.getMessage(), error);
+        }
+    }
 }
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 df5e37c..21b51b1 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -242,7 +242,7 @@
     private void closeInputDeviceDescriptorLocked(IBinder token,
             InputDeviceDescriptor inputDeviceDescriptor) {
         token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
-        mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+        mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer());
         String phys = inputDeviceDescriptor.getPhys();
         InputManager.getInstance().removeUniqueIdAssociation(phys);
         // Type associations are added in the case of navigation touchpads. Those should be removed
@@ -366,7 +366,7 @@
                 throw new IllegalArgumentException(
                         "Could not send key event to input device for given token");
             }
-            return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getFileDescriptor(),
+            return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getKeyCode(), event.getAction());
         }
     }
@@ -379,7 +379,7 @@
                 throw new IllegalArgumentException(
                         "Could not send key event to input device for given token");
             }
-            return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getFileDescriptor(),
+            return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getKeyCode(), event.getAction());
         }
     }
@@ -397,7 +397,7 @@
                 throw new IllegalStateException(
                         "Display id associated with this mouse is not currently targetable");
             }
-            return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getFileDescriptor(),
+            return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getButtonCode(), event.getAction());
         }
     }
@@ -410,7 +410,7 @@
                 throw new IllegalArgumentException(
                         "Could not send touch event to input device for given token");
             }
-            return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getFileDescriptor(),
+            return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getPointerId(), event.getToolType(), event.getAction(), event.getX(),
                     event.getY(), event.getPressure(), event.getMajorAxisSize());
         }
@@ -429,7 +429,7 @@
                 throw new IllegalStateException(
                         "Display id associated with this mouse is not currently targetable");
             }
-            return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getFileDescriptor(),
+            return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getRelativeX(), event.getRelativeY());
         }
     }
@@ -447,7 +447,7 @@
                 throw new IllegalStateException(
                         "Display id associated with this mouse is not currently targetable");
             }
-            return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getFileDescriptor(),
+            return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getXAxisMovement(), event.getYAxisMovement());
         }
     }
@@ -474,7 +474,7 @@
         synchronized (mLock) {
             fout.println("      Active descriptors: ");
             for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) {
-                fout.println("        fd: " + inputDeviceDescriptor.getFileDescriptor());
+                fout.println("        ptr: " + inputDeviceDescriptor.getNativePointer());
                 fout.println("          displayId: " + inputDeviceDescriptor.getDisplayId());
                 fout.println("          creationOrder: "
                         + inputDeviceDescriptor.getCreationOrderNumber());
@@ -487,10 +487,10 @@
     }
 
     @VisibleForTesting
-    void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId, String phys,
+    void addDeviceForTesting(IBinder deviceToken, long ptr, int type, int displayId, String phys,
             String deviceName, int inputDeviceId) {
         synchronized (mLock) {
-            mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(fd, () -> {
+            mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(ptr, () -> {
             }, type, displayId, phys, deviceName, inputDeviceId));
         }
     }
@@ -504,75 +504,76 @@
         return inputDeviceDescriptors;
     }
 
-    private static native int nativeOpenUinputDpad(String deviceName, int vendorId, int productId,
+    private static native long nativeOpenUinputDpad(String deviceName, int vendorId, int productId,
             String phys);
-    private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
+    private static native long nativeOpenUinputKeyboard(String deviceName, int vendorId,
             int productId, String phys);
-    private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
+    private static native long nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
             String phys);
-    private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
+    private static native long nativeOpenUinputTouchscreen(String deviceName, int vendorId,
             int productId, String phys, int height, int width);
-    private static native boolean nativeCloseUinput(int fd);
-    private static native boolean nativeWriteDpadKeyEvent(int fd, int androidKeyCode, int action);
-    private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
-    private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
-    private static native boolean nativeWriteTouchEvent(int fd, int pointerId, int toolType,
+    private static native void nativeCloseUinput(long ptr);
+    private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action);
+    private static native boolean nativeWriteKeyEvent(long ptr, int androidKeyCode, int action);
+    private static native boolean nativeWriteButtonEvent(long ptr, int buttonCode, int action);
+    private static native boolean nativeWriteTouchEvent(long ptr, int pointerId, int toolType,
             int action, float locationX, float locationY, float pressure, float majorAxisSize);
-    private static native boolean nativeWriteRelativeEvent(int fd, float relativeX,
+    private static native boolean nativeWriteRelativeEvent(long ptr, float relativeX,
             float relativeY);
-    private static native boolean nativeWriteScrollEvent(int fd, float xAxisMovement,
+    private static native boolean nativeWriteScrollEvent(long ptr, float xAxisMovement,
             float yAxisMovement);
 
     /** Wrapper around the static native methods for tests. */
     @VisibleForTesting
     protected static class NativeWrapper {
-        public int openUinputDpad(String deviceName, int vendorId, int productId, String phys) {
+        public long openUinputDpad(String deviceName, int vendorId, int productId, String phys) {
             return nativeOpenUinputDpad(deviceName, vendorId, productId, phys);
         }
 
-        public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
+        public long openUinputKeyboard(String deviceName, int vendorId, int productId,
+                String phys) {
             return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
         }
 
-        public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
+        public long openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
             return nativeOpenUinputMouse(deviceName, vendorId, productId, phys);
         }
 
-        public int openUinputTouchscreen(String deviceName, int vendorId,
+        public long openUinputTouchscreen(String deviceName, int vendorId,
                 int productId, String phys, int height, int width) {
             return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height,
                     width);
         }
 
-        public boolean closeUinput(int fd) {
-            return nativeCloseUinput(fd);
+        public void closeUinput(long ptr) {
+            nativeCloseUinput(ptr);
         }
 
-        public boolean writeDpadKeyEvent(int fd, int androidKeyCode, int action) {
-            return nativeWriteDpadKeyEvent(fd, androidKeyCode, action);
+        public boolean writeDpadKeyEvent(long ptr, int androidKeyCode, int action) {
+            return nativeWriteDpadKeyEvent(ptr, androidKeyCode, action);
         }
 
-        public boolean writeKeyEvent(int fd, int androidKeyCode, int action) {
-            return nativeWriteKeyEvent(fd, androidKeyCode, action);
+        public boolean writeKeyEvent(long ptr, int androidKeyCode, int action) {
+            return nativeWriteKeyEvent(ptr, androidKeyCode, action);
         }
 
-        public boolean writeButtonEvent(int fd, int buttonCode, int action) {
-            return nativeWriteButtonEvent(fd, buttonCode, action);
+        public boolean writeButtonEvent(long ptr, int buttonCode, int action) {
+            return nativeWriteButtonEvent(ptr, buttonCode, action);
         }
 
-        public boolean writeTouchEvent(int fd, int pointerId, int toolType, int action,
+        public boolean writeTouchEvent(long ptr, int pointerId, int toolType, int action,
                 float locationX, float locationY, float pressure, float majorAxisSize) {
-            return nativeWriteTouchEvent(fd, pointerId, toolType,
+            return nativeWriteTouchEvent(ptr, pointerId, toolType,
                     action, locationX, locationY,
                     pressure, majorAxisSize);
         }
 
-        public boolean writeRelativeEvent(int fd, float relativeX, float relativeY) {
-            return nativeWriteRelativeEvent(fd, relativeX, relativeY);
+        public boolean writeRelativeEvent(long ptr, float relativeX, float relativeY) {
+            return nativeWriteRelativeEvent(ptr, relativeX, relativeY);
         }
 
-        public boolean writeScrollEvent(int fd, float xAxisMovement, float yAxisMovement) {
-            return nativeWriteScrollEvent(fd, xAxisMovement,
+        public boolean writeScrollEvent(long ptr, float xAxisMovement, float yAxisMovement) {
+            return nativeWriteScrollEvent(ptr, xAxisMovement,
                     yAxisMovement);
         }
     }
@@ -597,7 +598,8 @@
 
         private static final AtomicLong sNextCreationOrderNumber = new AtomicLong(1);
 
-        private final int mFd;
+        // Pointer to the native input device object.
+        private final long mPtr;
         private final IBinder.DeathRecipient mDeathRecipient;
         private final @Type int mType;
         private final int mDisplayId;
@@ -611,9 +613,9 @@
         // Monotonically increasing number; devices with lower numbers were created earlier.
         private final long mCreationOrderNumber;
 
-        InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
+        InputDeviceDescriptor(long ptr, IBinder.DeathRecipient deathRecipient, @Type int type,
                 int displayId, String phys, String name, int inputDeviceId) {
-            mFd = fd;
+            mPtr = ptr;
             mDeathRecipient = deathRecipient;
             mType = type;
             mDisplayId = displayId;
@@ -623,8 +625,8 @@
             mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
         }
 
-        public int getFileDescriptor() {
-            return mFd;
+        public long getNativePointer() {
+            return mPtr;
         }
 
         public int getType() {
@@ -767,7 +769,7 @@
      */
     private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
             int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
-            Supplier<Integer> deviceOpener)
+            Supplier<Long> deviceOpener)
             throws DeviceCreationException {
         if (!mThreadVerifier.isValidThread()) {
             throw new IllegalStateException(
@@ -776,19 +778,22 @@
         }
         validateDeviceName(deviceName);
 
-        final int fd;
+        final long ptr;
         final BinderDeathRecipient binderDeathRecipient;
 
         final int inputDeviceId;
 
         setUniqueIdAssociation(displayId, phys);
         try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
-            fd = deviceOpener.get();
-            if (fd < 0) {
+            ptr = deviceOpener.get();
+            // See INVALID_PTR in libs/input/VirtualInputDevice.cpp.
+            if (ptr == 0) {
                 throw new DeviceCreationException(
-                        "A native error occurred when creating touchscreen: " + -fd);
+                        "A native error occurred when creating virtual input device: "
+                                + deviceName);
             }
-            // The fd is valid from here, so ensure that all failures close the fd after this point.
+            // The pointer to the native input device is valid from here, so ensure that all
+            // failures close the device after this point.
             try {
                 inputDeviceId = waiter.waitForDeviceCreation();
 
@@ -800,7 +805,7 @@
                             "Client died before virtual device could be created.", e);
                 }
             } catch (DeviceCreationException e) {
-                mNativeWrapper.closeUinput(fd);
+                mNativeWrapper.closeUinput(ptr);
                 throw e;
             }
         } catch (DeviceCreationException e) {
@@ -810,7 +815,7 @@
 
         synchronized (mLock) {
             mInputDeviceDescriptors.put(deviceToken,
-                    new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys,
+                    new InputDeviceDescriptor(ptr, binderDeathRecipient, type, displayId, phys,
                             deviceName, inputDeviceId));
         }
     }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 08ee6d7..61fc32d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -169,6 +169,7 @@
     @GuardedBy("mLock") int mDevCfgTextChangeFlushingFrequencyMs;
     @GuardedBy("mLock") int mDevCfgLogHistorySize;
     @GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
+    @GuardedBy("mLock") boolean mDevCfgDisableFlushForViewTreeAppearing;
 
     private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -359,6 +360,8 @@
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE:
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY:
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT:
+                case ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING:
                     setFineTuneParamsFromDeviceConfig();
                     return;
                 default:
@@ -388,13 +391,20 @@
                     DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                     ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
                     (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
+            mDevCfgDisableFlushForViewTreeAppearing = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                    ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                    false);
             if (verbose) {
                 Slog.v(TAG, "setFineTuneParamsFromDeviceConfig(): "
                         + "bufferSize=" + mDevCfgMaxBufferSize
                         + ", idleFlush=" + mDevCfgIdleFlushingFrequencyMs
                         + ", textFluxh=" + mDevCfgTextChangeFlushingFrequencyMs
                         + ", logHistory=" + mDevCfgLogHistorySize
-                        + ", idleUnbindTimeoutMs=" + mDevCfgIdleUnbindTimeoutMs);
+                        + ", idleUnbindTimeoutMs=" + mDevCfgIdleUnbindTimeoutMs
+                        + ", disableFlushForViewTreeAppearing="
+                        + mDevCfgDisableFlushForViewTreeAppearing);
             }
         }
     }
@@ -629,6 +639,7 @@
     }
 
     @Override // from AbstractMasterSystemService
+    @GuardedBy("mLock")
     protected void dumpLocked(String prefix, PrintWriter pw) {
         super.dumpLocked(prefix, pw);
 
@@ -646,6 +657,8 @@
         pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
         pw.print(prefix2); pw.print("idleUnbindTimeoutMs: ");
         pw.println(mDevCfgIdleUnbindTimeoutMs);
+        pw.print(prefix2); pw.print("disableFlushForViewTreeAppearing: ");
+        pw.println(mDevCfgDisableFlushForViewTreeAppearing);
         pw.print(prefix); pw.println("Global Options:");
         mGlobalContentCaptureOptions.dump(prefix2, pw);
     }
@@ -1005,12 +1018,15 @@
                 return null;
             }
 
-            final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
-                    mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
-                    mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
-                    whitelistedComponents);
-            if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
-            return options;
+            synchronized (mLock) {
+                final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
+                        mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
+                        mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
+                        mDevCfgDisableFlushForViewTreeAppearing,
+                        whitelistedComponents);
+                if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
+                return options;
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 57a0713..aaa376a 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2739,7 +2739,7 @@
                                     .setImsCallServiceType(prev.getImsCallServiceType())
                                     .setImsCallType(prev.getImsCallType()).build());
                 } else {
-                    log("There is no active call to report CallQaulity");
+                    log("There is no active call to report CallQuality");
                     return;
                 }
 
@@ -2771,6 +2771,18 @@
         // most purposes.
         final CellIdentity noLocationCi = cellIdentity.sanitizeLocationInfo();
 
+
+        // This shouldn't be necessary, but better to not take the chance
+        final String primaryPlmn = (cellIdentity != null) ? cellIdentity.getPlmn() : "<UNKNOWN>";
+
+        final String logStr = "Registration Failed for phoneId=" + phoneId
+                + " subId=" + subId + "primaryPlmn=" + primaryPlmn
+                + " chosenPlmn=" + chosenPlmn + " domain=" + domain
+                + " causeCode=" + causeCode + " additionalCauseCode=" + additionalCauseCode;
+
+        mLocalLog.log(logStr);
+        if (DBG) log(logStr);
+
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 for (Record r : mRecords) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6aa49d2..2045cca 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8613,7 +8613,7 @@
         // On Automotive / Headless System User Mode, at this point the system user has already been
         // started and unlocked, and some of the tasks we do here have already been done. So skip
         // those in that case. The duplicate system user start is guarded in SystemServiceManager.
-        // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
+        // TODO(b/266158156): this workaround shouldn't be necessary once we move the headless-user
         // start logic to UserManager-land.
         mUserController.onSystemUserStarting();
 
@@ -8646,7 +8646,7 @@
 
             // Some systems - like automotive - will explicitly unlock system user then switch
             // to a secondary user.
-            // TODO(b/242195409): this workaround shouldn't be necessary once we move
+            // TODO(b/266158156): this workaround shouldn't be necessary once we move
             // the headless-user start logic to UserManager-land.
             if (isBootingSystemUser && !UserManager.isHeadlessSystemUserMode()) {
                 t.traceBegin("startHomeOnAllDisplays");
@@ -8669,26 +8669,10 @@
                 final int callingPid = Binder.getCallingPid();
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
-                    broadcastIntentLocked(null, null, null, intent,
-                            null, null, 0, null, null, null, null, null, OP_NONE,
-                            null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
-                            currentUserId);
-                    intent = new Intent(Intent.ACTION_USER_STARTING);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
-                    broadcastIntentLocked(null, null, null, intent, null,
-                            new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered, boolean sticky,
-                                        int sendingUser) {}
-                            }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, null, null,
-                            OP_NONE, null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
-                            UserHandle.USER_ALL);
+                    mUserController.sendUserStartedBroadcast(
+                            currentUserId, callingUid, callingPid);
+                    mUserController.sendUserStartingBroadcast(
+                            currentUserId, callingUid, callingPid);
                 } catch (Throwable e) {
                     Slog.wtf(TAG, "Failed sending first user broadcasts", e);
                 } finally {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0e49c66..1b37883 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -365,13 +365,13 @@
     private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks;
 
     /**
-     * Messages for for switching from {@link android.os.UserHandle#SYSTEM}.
+     * Messages for switching from {@link android.os.UserHandle#SYSTEM}.
      */
     @GuardedBy("mLock")
     private String mSwitchingFromSystemUserMessage;
 
     /**
-     * Messages for for switching to {@link android.os.UserHandle#SYSTEM}.
+     * Messages for switching to {@link android.os.UserHandle#SYSTEM}.
      */
     @GuardedBy("mLock")
     private String mSwitchingToSystemUserMessage;
@@ -384,6 +384,16 @@
 
     private final LockPatternUtils mLockPatternUtils;
 
+    // TODO(b/266158156): remove this once we improve/refactor the way broadcasts are sent for
+    //  the system user in HSUM.
+    @GuardedBy("mLock")
+    private boolean mIsBroadcastSentForSystemUserStarted;
+
+    // TODO(b/266158156): remove this once we improve/refactor the way broadcasts are sent for
+    //  the system user in HSUM.
+    @GuardedBy("mLock")
+    private boolean mIsBroadcastSentForSystemUserStarting;
+
     volatile boolean mBootCompleted;
 
     /**
@@ -635,7 +645,7 @@
                 // user transitions to RUNNING_LOCKED.  However, in "headless system user mode", the
                 // system user is explicitly started before the device has finished booting.  In
                 // that case, we need to wait until onBootComplete() to send the broadcast.
-                if (!(UserManager.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) {
+                if (!(mInjector.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) {
                     // ACTION_LOCKED_BOOT_COMPLETED
                     sendLockedBootCompletedBroadcast(resultTo, userId);
                 }
@@ -1823,15 +1833,17 @@
                 needStart = false;
             }
 
-            if (needStart) {
-                // Send USER_STARTED broadcast
-                Intent intent = new Intent(Intent.ACTION_USER_STARTED);
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                        | Intent.FLAG_RECEIVER_FOREGROUND);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                mInjector.broadcastIntent(intent,
-                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                        null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
+            // In most cases, broadcast for the system user starting/started is sent by
+            // ActivityManagerService#systemReady(). However on some HSUM devices (e.g. tablets)
+            // the user switches from the system user to a secondary user while running
+            // ActivityManagerService#systemReady(), thus broadcast is not sent for the system user.
+            // Therefore we send the broadcast for the system user here as well in HSUM.
+            // TODO(b/266158156): Improve/refactor the way broadcasts are sent for the system user
+            // in HSUM. Ideally it'd be best to have one single place that sends this notification.
+            final boolean isSystemUserInHeadlessMode = (userId == UserHandle.USER_SYSTEM)
+                    && mInjector.isHeadlessSystemUserMode();
+            if (needStart || isSystemUserInHeadlessMode) {
+                sendUserStartedBroadcast(userId, callingUid, callingPid);
             }
             t.traceEnd();
 
@@ -1845,23 +1857,9 @@
                 t.traceEnd();
             }
 
-            if (needStart) {
+            if (needStart || isSystemUserInHeadlessMode) {
                 t.traceBegin("sendRestartBroadcast");
-                Intent intent = new Intent(Intent.ACTION_USER_STARTING);
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                mInjector.broadcastIntent(intent,
-                        null, new IIntentReceiver.Stub() {
-                            @Override
-                            public void performReceive(Intent intent, int resultCode,
-                                    String data, Bundle extras, boolean ordered,
-                                    boolean sticky,
-                                    int sendingUser) throws RemoteException {
-                            }
-                        }, 0, null, null,
-                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                        null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
-                        UserHandle.USER_ALL);
+                sendUserStartingBroadcast(userId, callingUid, callingPid);
                 t.traceEnd();
             }
         } finally {
@@ -2283,6 +2281,62 @@
         EventLogTags.writeAmSwitchUser(newUserId);
     }
 
+    // The two methods sendUserStartedBroadcast() and sendUserStartingBroadcast()
+    // could be merged for better reuse. However, the params they are calling broadcastIntent()
+    // with are different - resultCode receiver, permissions, ordered, and userId, etc. Therefore,
+    // we decided to keep two separate methods for better code readability/clarity.
+    // TODO(b/266158156): Improve/refactor the way broadcasts are sent for the system user
+    // in HSUM. Ideally it'd be best to have one single place that sends this notification.
+    /** Sends {@code ACTION_USER_STARTED} broadcast. */
+    void sendUserStartedBroadcast(@UserIdInt int userId, int callingUid, int callingPid) {
+        if (userId == UserHandle.USER_SYSTEM) {
+            synchronized (mLock) {
+                // Make sure that the broadcast is sent only once for the system user.
+                if (mIsBroadcastSentForSystemUserStarted) {
+                    return;
+                }
+                mIsBroadcastSentForSystemUserStarted = true;
+            }
+        }
+        final Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        mInjector.broadcastIntent(intent, /* resolvedType= */ null, /* resultTo= */ null,
+                /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
+                /* requiredPermissions= */ null, AppOpsManager.OP_NONE, /* bOptions= */ null,
+                /* ordered= */ false, /* sticky= */ false, MY_PID, SYSTEM_UID,
+                callingUid, callingPid, userId);
+    }
+
+    /** Sends {@code ACTION_USER_STARTING} broadcast. */
+    void sendUserStartingBroadcast(@UserIdInt int userId, int callingUid, int callingPid) {
+        if (userId == UserHandle.USER_SYSTEM) {
+            synchronized (mLock) {
+                // Make sure that the broadcast is sent only once for the system user.
+                if (mIsBroadcastSentForSystemUserStarting) {
+                    return;
+                }
+                mIsBroadcastSentForSystemUserStarting = true;
+            }
+        }
+        final Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        mInjector.broadcastIntent(intent, /* resolvedType= */ null,
+                new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode,
+                            String data, Bundle extras, boolean ordered,
+                            boolean sticky,
+                            int sendingUser) throws RemoteException {
+                    }
+                }, /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
+                new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE, /* bOptions= */ null,
+                /* ordered= */ true, /* sticky= */ false, MY_PID, SYSTEM_UID,
+                callingUid, callingPid, UserHandle.USER_ALL);
+    }
+
     void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
@@ -2568,7 +2622,7 @@
         for (int i = 0; i < startedUsers.size(); i++) {
             int userId = startedUsers.keyAt(i);
             UserState uss = startedUsers.valueAt(i);
-            if (!UserManager.isHeadlessSystemUserMode()) {
+            if (!mInjector.isHeadlessSystemUserMode()) {
                 finishUserBoot(uss, resultTo);
             } else {
                 if (userId == UserHandle.USER_SYSTEM) {
@@ -2589,9 +2643,9 @@
         mInjector.reportCurWakefulnessUsageEvent();
     }
 
-    // TODO(b/242195409): remove this method if initial system user boot logic is refactored?
+    // TODO(b/266158156): remove this method if initial system user boot logic is refactored?
     void onSystemUserStarting() {
-        if (!UserManager.isHeadlessSystemUserMode()) {
+        if (!mInjector.isHeadlessSystemUserMode()) {
             // Don't need to call on HSUM because it will be called when the system user is
             // restarted on background
             mInjector.onUserStarting(UserHandle.USER_SYSTEM);
@@ -3059,6 +3113,10 @@
             pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
             pw.println("  mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
             pw.println("  mInitialized:" + mInitialized);
+            pw.println("  mIsBroadcastSentForSystemUserStarted:"
+                    + mIsBroadcastSentForSystemUserStarted);
+            pw.println("  mIsBroadcastSentForSystemUserStarting:"
+                    + mIsBroadcastSentForSystemUserStarting);
             if (mSwitchingFromSystemUserMessage != null) {
                 pw.println("  mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage);
             }
@@ -3749,6 +3807,10 @@
             }, /* message= */ null);
         }
 
+        boolean isHeadlessSystemUserMode() {
+            return UserManager.isHeadlessSystemUserMode();
+        }
+
         boolean isUsersOnSecondaryDisplaysEnabled() {
             return UserManager.isVisibleBackgroundUsersEnabled();
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 6751153..399f6a8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -217,6 +217,16 @@
             public void onContextChanged(OperationContext context) {
                 Slog.w(TAG, "onContextChanged");
             }
+
+            @Override
+            public void onPointerCancelWithContext(PointerContext context) {
+                Slog.w(TAG, "onPointerCancelWithContext");
+            }
+
+            @Override
+            public void setIgnoreDisplayTouches(boolean shouldIgnore) {
+                Slog.w(TAG, "setIgnoreDisplayTouches");
+            }
         };
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c15e419..ab2c002 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -25,6 +25,8 @@
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
 import static android.os.PowerWhitelistManager.REASON_VPN;
 import static android.os.UserHandle.PER_USER_RANGE;
 
@@ -251,6 +253,14 @@
      */
     private static final int STARTING_TOKEN = -1;
 
+    // TODO : read this from carrier config instead of a constant
+    @VisibleForTesting
+    public static final int AUTOMATIC_KEEPALIVE_DELAY_SECONDS = 30;
+
+    // Default keepalive timeout for carrier config is 5 minutes. Mimic this.
+    @VisibleForTesting
+    static final int DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT = 5 * 60;
+
     // TODO: create separate trackers for each unique VPN to support
     // automated reconnection
 
@@ -3071,6 +3081,7 @@
                             prepareStatusIntent();
                         }
                         agentConnect(this::onValidationStatus);
+                        mSession.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
                         return; // Link properties are already sent.
                     } else {
                         // Underlying networks also set in agentConnect()
@@ -3179,6 +3190,7 @@
                     if (!removedAddrs.isEmpty()) {
                         startNewNetworkAgent(
                                 mNetworkAgent, "MTU too low for IPv6; restarting network agent");
+                        mSession.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
 
                         for (LinkAddress removed : removedAddrs) {
                             mTunnelIface.removeAddress(
@@ -3251,14 +3263,22 @@
         private IkeSessionParams getIkeSessionParams(@NonNull Network underlyingNetwork) {
             final IkeTunnelConnectionParams ikeTunConnParams =
                     mProfile.getIkeTunnelConnectionParams();
+            final IkeSessionParams.Builder builder;
             if (ikeTunConnParams != null) {
-                final IkeSessionParams.Builder builder =
-                        new IkeSessionParams.Builder(ikeTunConnParams.getIkeSessionParams())
-                                .setNetwork(underlyingNetwork);
-                return builder.build();
+                builder = new IkeSessionParams.Builder(ikeTunConnParams.getIkeSessionParams())
+                        .setNetwork(underlyingNetwork);
             } else {
-                return VpnIkev2Utils.buildIkeSessionParams(mContext, mProfile, underlyingNetwork);
+                builder = VpnIkev2Utils.makeIkeSessionParamsBuilder(mContext, mProfile,
+                        underlyingNetwork);
             }
+            if (mProfile.isAutomaticNattKeepaliveTimerEnabled()) {
+                builder.setNattKeepAliveDelaySeconds(guessNattKeepaliveTimerForNetwork());
+            }
+            if (mProfile.isAutomaticIpVersionSelectionEnabled()) {
+                builder.setIpVersion(guessEspIpVersionForNetwork());
+                builder.setEncapType(guessEspEncapTypeForNetwork());
+            }
+            return builder.build();
         }
 
         @NonNull
@@ -3322,6 +3342,23 @@
             startIkeSession(underlyingNetwork);
         }
 
+        private int guessEspIpVersionForNetwork() {
+            // TODO : guess the IP version based on carrier if auto IP version selection is enabled
+            return ESP_IP_VERSION_AUTO;
+        }
+
+        private int guessEspEncapTypeForNetwork() {
+            // TODO : guess the ESP encap type based on carrier if auto IP version selection is
+            // enabled
+            return ESP_ENCAP_TYPE_AUTO;
+        }
+
+        private int guessNattKeepaliveTimerForNetwork() {
+            // TODO : guess the keepalive delay based on carrier if auto keepalive timer is
+            // enabled
+            return AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+        }
+
         boolean maybeMigrateIkeSession(@NonNull Network underlyingNetwork) {
             if (mSession == null || !mMobikeEnabled) return false;
 
@@ -3331,7 +3368,20 @@
                     + mCurrentToken
                     + " to network "
                     + underlyingNetwork);
-            mSession.setNetwork(underlyingNetwork);
+            final int ipVersion = mProfile.isAutomaticIpVersionSelectionEnabled()
+                    ? guessEspIpVersionForNetwork() : ESP_IP_VERSION_AUTO;
+            final int encapType = mProfile.isAutomaticIpVersionSelectionEnabled()
+                    ? guessEspEncapTypeForNetwork() : ESP_ENCAP_TYPE_AUTO;
+            final int keepaliveDelaySeconds;
+            if (mProfile.isAutomaticNattKeepaliveTimerEnabled()) {
+                keepaliveDelaySeconds = guessNattKeepaliveTimerForNetwork();
+            } else if (mProfile.getIkeTunnelConnectionParams() != null) {
+                keepaliveDelaySeconds = mProfile.getIkeTunnelConnectionParams()
+                        .getIkeSessionParams().getNattKeepAliveDelaySeconds();
+            } else {
+                keepaliveDelaySeconds = DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+            }
+            mSession.setNetwork(underlyingNetwork, ipVersion, encapType, keepaliveDelaySeconds);
             return true;
         }
 
@@ -4661,8 +4711,14 @@
         }
 
         /** Update the underlying network of the IKE Session */
-        public void setNetwork(@NonNull Network network) {
-            mImpl.setNetwork(network);
+        public void setNetwork(@NonNull Network network, int ipVersion, int encapType,
+                int keepaliveDelaySeconds) {
+            mImpl.setNetwork(network, ipVersion, encapType, keepaliveDelaySeconds);
+        }
+
+        /** Set the underpinned network */
+        public void setUnderpinnedNetwork(@NonNull Network underpinnedNetwork) {
+            mImpl.setUnderpinnedNetwork(underpinnedNetwork);
         }
 
         /** Forcibly terminate the IKE Session */
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
index 857c86d..a48c9fc 100644
--- a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -99,7 +99,7 @@
 public class VpnIkev2Utils {
     private static final String TAG = VpnIkev2Utils.class.getSimpleName();
 
-    static IkeSessionParams buildIkeSessionParams(
+    static IkeSessionParams.Builder makeIkeSessionParamsBuilder(
             @NonNull Context context, @NonNull Ikev2VpnProfile profile, @NonNull Network network) {
         final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity());
         final IkeIdentification remoteId = parseIkeIdentification(profile.getServerAddr());
@@ -117,7 +117,7 @@
             ikeOptionsBuilder.addSaProposal(ikeProposal);
         }
 
-        return ikeOptionsBuilder.build();
+        return ikeOptionsBuilder;
     }
 
     static ChildSessionParams buildChildSessionParams(List<String> allowedAlgorithms) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 81e550e..6eb465e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -24,6 +24,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.hardware.display.DisplayManager.EventsMask;
+import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -38,8 +39,10 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
+import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -137,6 +140,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
@@ -232,6 +236,9 @@
     private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATIONS = 6;
     private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
     private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
+    private static final int[] EMPTY_ARRAY = new int[0];
+    private static final HdrConversionMode HDR_CONVERSION_MODE_UNSUPPORTED = new HdrConversionMode(
+            HDR_CONVERSION_UNSUPPORTED);
 
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
@@ -250,6 +257,10 @@
     @GuardedBy("mSyncRoot")
     private boolean mAreUserDisabledHdrTypesAllowed = true;
 
+    // This value indicates whether or not HDR output control is enabled.
+    // It is read from DeviceConfig and is updated via a listener if the config changes.
+    private volatile boolean mIsHdrOutputControlEnabled;
+
     // Display mode chosen by user.
     private Display.Mode mUserPreferredMode;
     // HDR conversion mode chosen by user
@@ -674,6 +685,11 @@
         synchronized (mSyncRoot) {
             mSafeMode = safeMode;
             mSystemReady = true;
+            mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled();
+            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER,
+                    BackgroundThread.getExecutor(),
+                    properties -> mIsHdrOutputControlEnabled =
+                            isDeviceConfigHdrOutputControlEnabled());
             // Just in case the top inset changed before the system was ready. At this point, any
             // relevant configuration should be in place.
             recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
@@ -681,7 +697,9 @@
             updateSettingsLocked();
             updateUserDisabledHdrTypesFromSettingsLocked();
             updateUserPreferredDisplayModeSettingsLocked();
-            updateHdrConversionModeSettingsLocked();
+            if (mIsHdrOutputControlEnabled) {
+                updateHdrConversionModeSettingsLocked();
+            }
         }
 
         mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -702,6 +720,12 @@
         mContext.registerReceiver(mIdleModeReceiver, filter);
     }
 
+    private boolean isDeviceConfigHdrOutputControlEnabled() {
+        return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER,
+                HDR_OUTPUT_CONTROL_FLAG,
+                true);
+    }
+
     @VisibleForTesting
     Handler getDisplayHandler() {
         return mHandler;
@@ -2117,7 +2141,7 @@
 
     private HdrConversionMode getHdrConversionModeSettingInternal() {
         if (!mInjector.getHdrOutputConversionSupport()) {
-            return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_UNSUPPORTED);
+            return HDR_CONVERSION_MODE_UNSUPPORTED;
         }
         synchronized (mSyncRoot) {
             if (mHdrConversionMode != null) {
@@ -2129,7 +2153,7 @@
 
     private HdrConversionMode getHdrConversionModeInternal() {
         if (!mInjector.getHdrOutputConversionSupport()) {
-            return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_UNSUPPORTED);
+            return HDR_CONVERSION_MODE_UNSUPPORTED;
         }
         HdrConversionMode mode;
         synchronized (mSyncRoot) {
@@ -3948,6 +3972,9 @@
 
         @Override // Binder call
         public void setHdrConversionMode(HdrConversionMode hdrConversionMode) {
+            if (!mIsHdrOutputControlEnabled) {
+                return;
+            }
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.MODIFY_HDR_CONVERSION_MODE,
                     "Permission required to set the HDR conversion mode.");
@@ -3961,6 +3988,9 @@
 
         @Override // Binder call
         public HdrConversionMode getHdrConversionModeSetting() {
+            if (!mIsHdrOutputControlEnabled) {
+                return HDR_CONVERSION_MODE_UNSUPPORTED;
+            }
             final long token = Binder.clearCallingIdentity();
             try {
                 return getHdrConversionModeSettingInternal();
@@ -3971,6 +4001,9 @@
 
         @Override // Binder call
         public HdrConversionMode getHdrConversionMode() {
+            if (!mIsHdrOutputControlEnabled) {
+                return HDR_CONVERSION_MODE_UNSUPPORTED;
+            }
             final long token = Binder.clearCallingIdentity();
             try {
                 return getHdrConversionModeInternal();
@@ -3982,6 +4015,9 @@
         @Display.HdrCapabilities.HdrType
         @Override // Binder call
         public int[] getSupportedHdrOutputTypes() {
+            if (!mIsHdrOutputControlEnabled) {
+                return EMPTY_ARRAY;
+            }
             final long token = Binder.clearCallingIdentity();
             try {
                 return getSupportedHdrOutputTypesInternal();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 38cddc4..0d39457 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -229,7 +229,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.VibrationEffect;
-import android.permission.PermissionCheckerManager;
 import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -6774,7 +6773,7 @@
                 final int permissionResult = mPermissionManager.checkPermissionForDataDelivery(
                         Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null);
 
-                if (permissionResult != PermissionCheckerManager.PERMISSION_GRANTED) {
+                if (permissionResult != PermissionManager.PERMISSION_GRANTED) {
                     makeStickyHun(notification);
                 }
 
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 84ea077..9eb73aa 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -401,7 +401,7 @@
     /*
      Allowing view action from clone to parent profile to open any app-links or web links
      */
-    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_VIEW_ACTION =
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_WEB_VIEW_ACTION =
             new DefaultCrossProfileIntentFilter.Builder(
                     DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
                     /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
@@ -415,7 +415,7 @@
     /*
      Allowing view action from parent to clone profile to open any app-links or web links
      */
-    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_VIEW_ACTION =
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_WEB_VIEW_ACTION =
             new DefaultCrossProfileIntentFilter.Builder(
                     DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
                     /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
@@ -427,6 +427,21 @@
                     .build();
 
     /*
+     Allowing view action from clone to parent profile to any data type e.g. pdf, including custom
+     content providers.
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_VIEW_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addDataType("*/*")
+                    .build();
+
+
+    /*
      Allowing pick,insert and edit action from clone to parent profile to open picker or contacts
      insert/edit.
      */
@@ -441,7 +456,10 @@
                     .addAction(Intent.ACTION_EDIT)
                     .addAction(Intent.ACTION_INSERT)
                     .addAction(Intent.ACTION_INSERT_OR_EDIT)
+                    .addAction(Intent.ACTION_OPEN_DOCUMENT)
                     .addDataType("*/*")
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_OPENABLE)
                     .build();
 
     /*
@@ -458,19 +476,71 @@
                     .addAction(Intent.ACTION_EDIT)
                     .addAction(Intent.ACTION_INSERT)
                     .addAction(Intent.ACTION_INSERT_OR_EDIT)
+                    .addAction(Intent.ACTION_OPEN_DOCUMENT)
                     .addDataType("*/*")
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_OPENABLE)
+                    .build();
+
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_DIAL_DATA =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_DIAL)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataScheme("tel")
+                    .addDataScheme("sip")
+                    .addDataScheme("voicemail")
+                    .build();
+
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_DIAL_DATA =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_DIAL)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataScheme("tel")
+                    .addDataScheme("sip")
+                    .addDataScheme("voicemail")
+                    .build();
+
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_SMS_MMS =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addAction(Intent.ACTION_SENDTO)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataScheme("sms")
+                    .addDataScheme("smsto")
+                    .addDataScheme("mms")
+                    .addDataScheme("mmsto")
                     .build();
 
     public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
         return Arrays.asList(
                 PARENT_TO_CLONE_SEND_ACTION,
-                PARENT_TO_CLONE_VIEW_ACTION,
+                PARENT_TO_CLONE_WEB_VIEW_ACTION,
                 PARENT_TO_CLONE_PICK_INSERT_ACTION,
+                PARENT_TO_CLONE_DIAL_DATA,
                 CLONE_TO_PARENT_MEDIA_CAPTURE,
                 CLONE_TO_PARENT_SEND_ACTION,
+                CLONE_TO_PARENT_WEB_VIEW_ACTION,
                 CLONE_TO_PARENT_VIEW_ACTION,
-                CLONE_TO_PARENT_PICK_INSERT_ACTION
-
+                CLONE_TO_PARENT_PICK_INSERT_ACTION,
+                CLONE_TO_PARENT_DIAL_DATA,
+                CLONE_TO_PARENT_SMS_MMS
         );
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3545747..d21274a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2159,7 +2159,7 @@
 
         boolean appActivityEmbeddingEnabled = false;
         try {
-            appActivityEmbeddingEnabled = WindowManagerService.sWindowExtensionsEnabled
+            appActivityEmbeddingEnabled = WindowManager.hasWindowExtensionsEnabled()
                     && mAtmService.mContext.getPackageManager()
                             .getProperty(PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED, packageName)
                             .getBoolean();
@@ -5225,7 +5225,16 @@
                 Debug.getCallers(6));
 
         // Before setting mVisibleRequested so we can track changes.
-        mTransitionController.collect(this);
+        boolean isCollecting = 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));
+            }
+        }
 
         onChildVisibilityRequested(visible);
 
@@ -5297,9 +5306,9 @@
         }
 
         // Defer committing visibility until transition starts.
-        if (inTransition()) {
-            if (!visible && mTransitionController.inPlayingTransition(this)
-                    && mTransitionController.isCollecting(this)) {
+        if (isCollecting) {
+            // It may be occluded by the activity above that calls convertFromTranslucent().
+            if (!visible && mTransitionController.inPlayingTransition(this)) {
                 mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
             }
             return;
@@ -5319,11 +5328,11 @@
      * Then its visibility will be committed until the transition is ready.
      */
     private boolean deferCommitVisibilityChange(boolean visible) {
+        if (mTransitionController.isShellTransitionsEnabled()) {
+            // Shell transition doesn't use opening/closing sets.
+            return false;
+        }
         if (!mDisplayContent.mAppTransition.isTransitionSet()) {
-            if (mTransitionController.isShellTransitionsEnabled()) {
-                // Shell transition doesn't use opening/closing sets.
-                return false;
-            }
             // Defer committing visibility for non-home app which is animating by recents.
             if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
                 return false;
@@ -7978,6 +7987,14 @@
     }
 
     /**
+     * @return The {@code true} if the current instance has {@link mCompatDisplayInsets} without
+     * considering the inheritance implemented in {@link #getCompatDisplayInsets()}
+     */
+    boolean hasCompatDisplayInsetsWithoutInheritance() {
+        return mCompatDisplayInsets != null;
+    }
+
+    /**
      * @return {@code true} if this activity is in size compatibility mode that uses the different
      *         density than its parent or its bounds don't fit in parent naturally.
      */
@@ -7985,7 +8002,7 @@
         if (mInSizeCompatModeForBounds) {
             return true;
         }
-        if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets()
+        if (getCompatDisplayInsets() == null || !shouldCreateCompatDisplayInsets()
                 // The orientation is different from parent when transforming.
                 || isFixedRotationTransforming()) {
             return false;
@@ -8056,11 +8073,7 @@
 
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
     private void updateCompatDisplayInsets() {
-        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
-            mCompatDisplayInsets =  mLetterboxUiController.getInheritedCompatDisplayInsets();
-            return;
-        }
-        if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
+        if (getCompatDisplayInsets() != null || !shouldCreateCompatDisplayInsets()) {
             // The override configuration is set only once in size compatibility mode.
             return;
         }
@@ -8123,9 +8136,6 @@
 
     @Override
     float getCompatScale() {
-        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
-            return mLetterboxUiController.getInheritedSizeCompatScale();
-        }
         return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
     }
 
@@ -8172,7 +8182,7 @@
             resolveFixedOrientationConfiguration(newParentConfiguration);
         }
 
-        if (mCompatDisplayInsets != null) {
+        if (getCompatDisplayInsets() != null) {
             resolveSizeCompatModeConfiguration(newParentConfiguration);
         } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
             // We ignore activities' requested orientation in multi-window modes. They may be
@@ -8190,7 +8200,7 @@
             resolveAspectRatioRestriction(newParentConfiguration);
         }
 
-        if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null
+        if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null
                 // In fullscreen, can be letterboxed for aspect ratio.
                 || !inMultiWindowMode()) {
             updateResolvedBoundsPosition(newParentConfiguration);
@@ -8198,7 +8208,8 @@
 
         boolean isIgnoreOrientationRequest = mDisplayContent != null
                 && mDisplayContent.getIgnoreOrientationRequest();
-        if (mCompatDisplayInsets == null // for size compat mode set in updateCompatDisplayInsets
+        if (getCompatDisplayInsets() == null
+                // for size compat mode set in updateCompatDisplayInsets
                 // Fixed orientation letterboxing is possible on both large screen devices
                 // with ignoreOrientationRequest enabled and on phones in split screen even with
                 // ignoreOrientationRequest disabled.
@@ -8244,7 +8255,7 @@
                         info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
                         info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
                         !matchParentBounds(),
-                        mCompatDisplayInsets != null,
+                        getCompatDisplayInsets() != null,
                         shouldCreateCompatDisplayInsets());
             }
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8551,8 +8562,9 @@
                 || orientationRespectedWithInsets)) {
             return;
         }
+        final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
 
-        if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) {
+        if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
             // App prefers to keep its original size.
             // If the size compat is from previous fixed orientation letterboxing, we may want to
             // have fixed orientation letterbox again, otherwise it will show the size compat
@@ -8607,9 +8619,9 @@
         mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
                 containingBounds, desiredAspectRatio);
 
-        if (mCompatDisplayInsets != null) {
-            mCompatDisplayInsets.getBoundsByRotation(
-                    mTmpBounds, newParentConfig.windowConfiguration.getRotation());
+        if (compatDisplayInsets != null) {
+            compatDisplayInsets.getBoundsByRotation(mTmpBounds,
+                    newParentConfig.windowConfiguration.getRotation());
             if (resolvedBounds.width() != mTmpBounds.width()
                     || resolvedBounds.height() != mTmpBounds.height()) {
                 // The app shouldn't be resized, we only do fixed orientation letterboxing if the
@@ -8623,7 +8635,7 @@
         // Calculate app bounds using fixed orientation bounds because they will be needed later
         // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
         getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
-                newParentConfig, mCompatDisplayInsets);
+                newParentConfig, compatDisplayInsets);
         mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
     }
 
@@ -8680,13 +8692,13 @@
                 ? requestedOrientation
                 // We should use the original orientation of the activity when possible to avoid
                 // forcing the activity in the opposite orientation.
-                : mCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
-                        ? mCompatDisplayInsets.mOriginalRequestedOrientation
+                : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+                        ? getCompatDisplayInsets().mOriginalRequestedOrientation
                         : newParentConfiguration.orientation;
         int rotation = newParentConfiguration.windowConfiguration.getRotation();
         final boolean isFixedToUserRotation = mDisplayContent == null
                 || mDisplayContent.getDisplayRotation().isFixedToUserRotation();
-        if (!isFixedToUserRotation && !mCompatDisplayInsets.mIsFloating) {
+        if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) {
             // Use parent rotation because the original display can be rotated.
             resolvedConfig.windowConfiguration.setRotation(rotation);
         } else {
@@ -8702,11 +8714,11 @@
         // rely on them to contain the original and unchanging width and height of the app.
         final Rect containingAppBounds = new Rect();
         final Rect containingBounds = mTmpBounds;
-        mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
+        getCompatDisplayInsets().getContainerBounds(containingAppBounds, containingBounds, rotation,
                 orientation, orientationRequested, isFixedToUserRotation);
         resolvedBounds.set(containingBounds);
         // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
-        if (!mCompatDisplayInsets.mIsFloating) {
+        if (!getCompatDisplayInsets().mIsFloating) {
             mIsAspectRatioApplied =
                     applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
         }
@@ -8715,7 +8727,7 @@
         // are calculated in compat container space. The actual position on screen will be applied
         // later, so the calculation is simpler that doesn't need to involve offset from parent.
         getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                mCompatDisplayInsets);
+                getCompatDisplayInsets());
         // Use current screen layout as source because the size of app is independent to parent.
         resolvedConfig.screenLayout = computeScreenLayout(
                 getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
@@ -8750,14 +8762,9 @@
 
         // Calculates the scale the size compatibility bounds into the region which is available
         // to application.
-        final int contentW = resolvedAppBounds.width();
-        final int contentH = resolvedAppBounds.height();
-        final int viewportW = containerAppBounds.width();
-        final int viewportH = containerAppBounds.height();
         final float lastSizeCompatScale = mSizeCompatScale;
-        // Only allow to scale down.
-        mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH)
-                ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH);
+        updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
+
         final int containerTopInset = containerAppBounds.top - containerBounds.top;
         final boolean topNotAligned =
                 containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
@@ -8797,6 +8804,20 @@
                 isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds);
     }
 
+    void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
+        // Only allow to scale down.
+        mSizeCompatScale = mLetterboxUiController.findOpaqueNotFinishingActivityBelow()
+                .map(activityRecord -> activityRecord.mSizeCompatScale)
+                .orElseGet(() -> {
+                    final int contentW = resolvedAppBounds.width();
+                    final int contentH = resolvedAppBounds.height();
+                    final int viewportW = containerAppBounds.width();
+                    final int viewportH = containerAppBounds.height();
+                    return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min(
+                            (float) viewportW / contentW, (float) viewportH / contentH);
+                });
+    }
+
     private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
         if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
             // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
@@ -8859,10 +8880,16 @@
 
     @Override
     public Rect getBounds() {
-        if (mSizeCompatBounds != null) {
-            return mSizeCompatBounds;
-        }
-        return super.getBounds();
+        // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
+        final Rect superBounds = super.getBounds();
+        return mLetterboxUiController.findOpaqueNotFinishingActivityBelow()
+                .map(ActivityRecord::getBounds)
+                .orElseGet(() -> {
+                    if (mSizeCompatBounds != null) {
+                        return mSizeCompatBounds;
+                    }
+                    return superBounds;
+                });
     }
 
     @Override
@@ -8887,7 +8914,7 @@
         // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it
         // will keep the same bounds and screen configuration when it was first launched regardless
         // how its parent window changes, so that the sandbox API will provide a consistent result.
-        if (mCompatDisplayInsets != null || shouldCreateCompatDisplayInsets()) {
+        if (getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()) {
             return true;
         }
 
@@ -8929,7 +8956,7 @@
                 mTransitionController.collect(this);
             }
         }
-        if (mCompatDisplayInsets != null) {
+        if (getCompatDisplayInsets() != null) {
             Configuration overrideConfig = getRequestedOverrideConfiguration();
             // Adapt to changes in orientation locking. The app is still non-resizable, but
             // it can change which orientation is fixed. If the fixed orientation changes,
@@ -9005,7 +9032,7 @@
         if (mVisibleRequested) {
             // It may toggle the UI for user to restart the size compatibility mode activity.
             display.handleActivitySizeCompatModeIfNeeded(this);
-        } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard
+        } else if (getCompatDisplayInsets() != null && !visibleIgnoringKeyguard
                 && (app == null || !app.hasVisibleActivities())) {
             // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during
             // displays change. Displays are turned off during the change so mVisibleRequested
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index dcafe80..0dc6e0f 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -217,6 +217,34 @@
         if (DEBUG) Slog.d(TAG, "Requested to sync draw transaction");
     }
 
+    /**
+     * If an async window is not requested to redraw or its surface is removed, then complete its
+     * operation directly to avoid waiting until timeout.
+     */
+    void updateTargetWindows() {
+        if (mTransitionOp == OP_LEGACY || !mIsStartTransactionCommitted) return;
+        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+            final Operation op = mTargetWindowTokens.valueAt(i);
+            if (op.mIsCompletionPending || op.mAction == Operation.ACTION_SEAMLESS) {
+                // Skip completed target. And seamless windows use the signal from blast sync.
+                continue;
+            }
+            final WindowToken token = mTargetWindowTokens.keyAt(i);
+            int readyCount = 0;
+            final int childCount = token.getChildCount();
+            for (int j = childCount - 1; j >= 0; j--) {
+                final WindowState w = token.getChildAt(j);
+                // If the token no longer contains pending drawn windows, then it is ready.
+                if (w.isDrawn() || !w.mWinAnimator.getShown()) {
+                    readyCount++;
+                }
+            }
+            if (readyCount == childCount) {
+                mDisplayContent.finishAsyncRotation(token);
+            }
+        }
+    }
+
     /** Lets the window fit in new rotation naturally. */
     private void finishOp(WindowToken windowToken) {
         final Operation op = mTargetWindowTokens.remove(windowToken);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ade2fe7..17ec9cb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4761,6 +4761,9 @@
 
     void updateWindowsForAnimator() {
         forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
+        if (mAsyncRotationController != null) {
+            mAsyncRotationController.updateTargetWindows();
+        }
     }
 
     boolean isInputMethodClientFocus(int uid, int pid) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 5136670..fa49a6ba 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -22,6 +22,7 @@
 import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
 import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS;
 import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -296,7 +297,6 @@
                 R.bool.config_isCompatFakeFocusEnabled);
         mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
-
         mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
         mDeviceConfig.updateFlagActiveStatus(
@@ -311,7 +311,9 @@
         mDeviceConfig.updateFlagActiveStatus(
                 /* isActive */ mIsCompatFakeFocusEnabled,
                 /* key */ KEY_ENABLE_COMPAT_FAKE_FOCUS);
-
+        mDeviceConfig.updateFlagActiveStatus(
+                /* isActive */ mTranslucentLetterboxingEnabled,
+                /* key */ KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY);
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
     }
@@ -1003,7 +1005,7 @@
 
     boolean isTranslucentLetterboxingEnabled() {
         return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
-                && isTranslucentLetterboxingAllowed());
+                && mDeviceConfig.getFlag(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY));
     }
 
     void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) {
@@ -1051,13 +1053,6 @@
                 isDeviceInTabletopMode, nextVerticalPosition);
     }
 
-    // TODO(b/262378106): Cache a runtime flag and implement
-    // DeviceConfig.OnPropertiesChangedListener
-    static boolean isTranslucentLetterboxingAllowed() {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                "enable_translucent_activity_letterbox", false);
-    }
-
     /** Whether fake sending focus is enabled for unfocused apps in splitscreen */
     boolean isCompatFakeFocusEnabled() {
         return mIsCompatFakeFocusEnabled && mDeviceConfig.getFlag(KEY_ENABLE_COMPAT_FAKE_FOCUS);
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index b364872..df3c8f0 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -48,6 +48,11 @@
     static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus";
     private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true;
 
+    static final String KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY =
+            "enable_letterbox_translucent_activity";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true;
+
     @VisibleForTesting
     static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
             KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
@@ -57,7 +62,9 @@
             KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
             DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST,
             KEY_ENABLE_COMPAT_FAKE_FOCUS,
-            DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS
+            DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS,
+            KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
+            DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY
     );
 
     // Whether camera compatibility treatment is enabled.
@@ -82,6 +89,10 @@
     // which isn't guaranteed by default in multi-window modes.
     private boolean mIsCompatFakeFocusAllowed = DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS;
 
+    // Whether the letterbox strategy for transparent activities is allowed
+    private boolean mIsTranslucentLetterboxingAllowed =
+            DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY;
+
     // Set of active device configs that need to be updated in
     // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
     private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -129,6 +140,8 @@
                 return mIsAllowIgnoreOrientationRequest;
             case KEY_ENABLE_COMPAT_FAKE_FOCUS:
                 return mIsCompatFakeFocusAllowed;
+            case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
+                return mIsTranslucentLetterboxingAllowed;
             default:
                 throw new AssertionError("Unexpected flag name: " + key);
         }
@@ -141,20 +154,20 @@
         }
         switch (key) {
             case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
-                mIsCameraCompatTreatmentEnabled =
-                        getDeviceConfig(key, defaultValue);
+                mIsCameraCompatTreatmentEnabled = getDeviceConfig(key, defaultValue);
                 break;
             case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
                 mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
                         getDeviceConfig(key, defaultValue);
                 break;
             case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
-                mIsAllowIgnoreOrientationRequest =
-                        getDeviceConfig(key, defaultValue);
+                mIsAllowIgnoreOrientationRequest = getDeviceConfig(key, defaultValue);
                 break;
             case KEY_ENABLE_COMPAT_FAKE_FOCUS:
-                mIsCompatFakeFocusAllowed =
-                        getDeviceConfig(key, defaultValue);
+                mIsCompatFakeFocusAllowed = getDeviceConfig(key, defaultValue);
+                break;
+            case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
+                mIsTranslucentLetterboxingAllowed = getDeviceConfig(key, defaultValue);
                 break;
             default:
                 throw new AssertionError("Unexpected flag name: " + key);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index e343768..e1dbe01a 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -193,12 +193,6 @@
     // The app compat state for the opaque activity if any
     private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
 
-    // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode.
-    private boolean mIsInheritedInSizeCompatMode;
-
-    // This is the SizeCompatScale of the opaque activity beneath a translucent one
-    private float mInheritedSizeCompatScale;
-
     // The CompatDisplayInsets of the opaque activity beneath the translucent one.
     private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
 
@@ -735,8 +729,21 @@
                     : mActivityRecord.inMultiWindowMode()
                             ? mActivityRecord.getTask().getBounds()
                             : mActivityRecord.getRootTask().getParent().getBounds();
+            // In case of translucent activities an option is to use the WindowState#getFrame() of
+            // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
+            // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct
+            // information and in particular it might provide a value for a smaller area making
+            // the letterbox overlap with the translucent activity's frame.
+            // If we use WindowState#getFrame() for the translucent activity's letterbox inner
+            // frame, the letterbox will then be overlapped with the translucent activity's frame.
+            // Because the surface layer of letterbox is lower than an activity window, this
+            // won't crop the content, but it may affect other features that rely on values stored
+            // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher
+            // For this reason we use ActivityRecord#getBounds() that the translucent activity
+            // inherits from the first opaque activity beneath and also takes care of the scaling
+            // in case of activities in size compat mode.
             final Rect innerFrame = hasInheritedLetterboxBehavior()
-                    ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame();
+                    ? mActivityRecord.getBounds() : w.getFrame();
             mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
         } else if (mLetterbox != null) {
             mLetterbox.hide();
@@ -1386,10 +1393,10 @@
             mLetterboxConfigListener.onRemoved();
             clearInheritedConfig();
         }
-        // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the
+        // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
         // opaque activity constraints because we're expecting the activity is already letterboxed.
-        if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null
-                || mActivityRecord.fillsParent()) {
+        if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
+                || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) {
             return;
         }
         final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
@@ -1417,6 +1424,7 @@
                     // We need to initialize appBounds to avoid NPE. The actual value will
                     // be set ahead when resolving the Configuration for the activity.
                     mutatedConfiguration.windowConfiguration.setAppBounds(new Rect());
+                    inheritConfiguration(firstOpaqueActivityBeneath);
                     return mutatedConfiguration;
                 });
     }
@@ -1457,16 +1465,12 @@
         return mInheritedAppCompatState;
     }
 
-    float getInheritedSizeCompatScale() {
-        return mInheritedSizeCompatScale;
-    }
-
     @Configuration.Orientation
     int getInheritedOrientation() {
         return mInheritedOrientation;
     }
 
-    public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
+    ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
         return mInheritedCompatDisplayInsets;
     }
 
@@ -1486,7 +1490,7 @@
      * @return The first not finishing opaque activity beneath the current translucent activity
      * if it exists and the strategy is enabled.
      */
-    private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() {
+    Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() {
         if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) {
             return Optional.empty();
         }
@@ -1508,8 +1512,6 @@
         }
         mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation();
         mInheritedAppCompatState = firstOpaque.getAppCompatState();
-        mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode();
-        mInheritedSizeCompatScale = firstOpaque.getCompatScale();
         mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets();
     }
 
@@ -1519,8 +1521,6 @@
         mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
         mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
         mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
-        mIsInheritedInSizeCompatMode = false;
-        mInheritedSizeCompatScale = 1f;
         mInheritedCompatDisplayInsets = null;
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c7d8553..2af5460 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3389,6 +3389,11 @@
                 && top.getOrganizedTask() == this && top.isState(RESUMED);
         // Whether the direct top activity is in size compat mode on foreground.
         info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode();
+        if (info.topActivityInSizeCompat
+                && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+            // We hide the restart button in case of transparent activities.
+            info.topActivityInSizeCompat = top.fillsParent();
+        }
         // Whether the direct top activity is eligible for letterbox education.
         info.topActivityEligibleForLetterboxEducation = isTopActivityResumed
                 && top.isEligibleForLetterboxEducation();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 887f33e..98563f6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -443,16 +443,6 @@
     static final boolean ENABLE_FIXED_ROTATION_TRANSFORM =
             SystemProperties.getBoolean("persist.wm.fixed_rotation_transform", true);
 
-    /**
-     * Whether the device supports the WindowManager Extensions.
-     * OEMs can enable this by having their device config to inherit window_extensions.mk, such as:
-     * <pre>
-     * $(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk)
-     * </pre>
-     */
-    static final boolean sWindowExtensionsEnabled =
-            SystemProperties.getBoolean("persist.wm.extensions.enabled", false);
-
     // Enums for animation scale update types.
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
@@ -7367,7 +7357,8 @@
                 ProtoLog.w(WM_ERROR, "unable to restore pointer icon");
             }
         } else {
-            InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_DEFAULT);
+            mContext.getSystemService(InputManager.class)
+                    .setPointerIconType(PointerIcon.TYPE_DEFAULT);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index f9592cc..8e22821 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -315,6 +315,13 @@
                     }
                     transition = mTransitionController.createTransition(type);
                 }
+                if (!transition.isCollecting()) {
+                    Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
+                            + " means Shell took too long to respond to a request. WM State may be"
+                            + " incorrect now, please file a bug");
+                    applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
+                    return transition.getToken();
+                }
                 transition.start();
                 transition.mLogger.mStartWCT = wct;
                 applyTransaction(wct, -1 /*syncId*/, transition, caller);
@@ -1900,16 +1907,13 @@
         }
         ownerTask.addChild(taskFragment, position);
         taskFragment.setWindowingMode(creationParams.getWindowingMode());
-        if (creationParams.areInitialRelativeBoundsSet()) {
+        if (!creationParams.getInitialRelativeBounds().isEmpty()) {
             // Set relative bounds instead of using setBounds. This will avoid unnecessary update in
             // case the parent has resized since the last time parent info is sent to the organizer.
             taskFragment.setRelativeEmbeddedBounds(creationParams.getInitialRelativeBounds());
             // Recompute configuration as the bounds will be calculated based on relative bounds in
             // TaskFragment#resolveOverrideConfiguration.
             taskFragment.recomputeConfiguration();
-        } else {
-            // TODO(b/232476698): remove after remove creationParams.getInitialBounds().
-            taskFragment.setBounds(creationParams.getInitialBounds());
         }
         mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a102986..e8625bc 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -32,7 +32,6 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -424,7 +423,7 @@
 
         computeShownFrameLocked();
 
-        if (w.isParentWindowHidden() || !w.isOnScreen()) {
+        if (!w.isOnScreen()) {
             hide(t, "prepareSurfaceLocked");
             mWallpaperControllerLocked.hideWallpapers(w);
 
@@ -449,30 +448,23 @@
 
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
-                    if (showSurfaceRobustlyLocked(t)) {
-                        mAnimator.requestRemovalOfReplacedWindows(w);
-                        mLastHidden = false;
-                        final DisplayContent displayContent = w.getDisplayContent();
-                        if (!displayContent.getLastHasContent()) {
-                            // This draw means the difference between unique content and mirroring.
-                            // Run another pass through performLayout to set mHasContent in the
-                            // LogicalDisplay.
-                            displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
-                            if (DEBUG_LAYOUT_REPEATS) {
-                                mService.mWindowPlacerLocked.debugLayoutRepeats(
-                                        "showSurfaceRobustlyLocked " + w,
-                                        displayContent.pendingLayoutChanges);
-                            }
+                    mSurfaceController.showRobustly(t);
+                    mAnimator.requestRemovalOfReplacedWindows(w);
+                    mLastHidden = false;
+                    final DisplayContent displayContent = w.getDisplayContent();
+                    if (!displayContent.getLastHasContent()) {
+                        // This draw means the difference between unique content and mirroring.
+                        // Run another pass through performLayout to set mHasContent in the
+                        // LogicalDisplay.
+                        displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
+                        if (DEBUG_LAYOUT_REPEATS) {
+                            mService.mWindowPlacerLocked.debugLayoutRepeats(
+                                    "showSurfaceRobustlyLocked " + w,
+                                    displayContent.pendingLayoutChanges);
                         }
-                    } else {
-                        w.setOrientationChanging(false);
                     }
                 }
             }
-        } else {
-            if (mWin.isAnimating(TRANSITION | PARENTS)) {
-                ProtoLog.v(WM_DEBUG_ANIM, "prepareSurface: No changes in animation for %s", this);
-            }
         }
 
         if (w.getOrientationChanging()) {
@@ -511,22 +503,6 @@
         mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
     }
 
-    /**
-     * Have the surface flinger show a surface, robustly dealing with
-     * error conditions.  In particular, if there is not enough memory
-     * to show the surface, then we will try to get rid of other surfaces
-     * in order to succeed.
-     *
-     * @return Returns true if the surface was successfully shown.
-     */
-    private boolean showSurfaceRobustlyLocked(SurfaceControl.Transaction t) {
-        boolean shown = mSurfaceController.showRobustly(t);
-        if (!shown)
-            return false;
-
-        return true;
-    }
-
     void applyEnterAnimationLocked() {
         // If we are the new part of a window replacement transition and we have requested
         // not to animate, we instead want to make it seamless, so we don't want to apply
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 607ce25..33751b9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -245,13 +245,13 @@
         t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
     }
 
-    boolean showRobustly(SurfaceControl.Transaction t) {
+    void showRobustly(SurfaceControl.Transaction t) {
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
         if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
                 + " during relayout");
 
         if (mSurfaceShown) {
-            return true;
+            return;
         }
 
         setShown(true);
@@ -262,7 +262,6 @@
                     dc.mDisplayId, 1 /* request shown */,
                     String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
         }
-        return true;
     }
 
     boolean clearWindowContentFrameStats() {
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index dd757bc..4898d95 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <input/Input.h>
+#include <input/VirtualInputDevice.h>
 #include <linux/uinput.h>
 #include <math.h>
 #include <nativehelper/JNIHelp.h>
@@ -32,16 +33,12 @@
 #include <set>
 #include <string>
 
-/**
- * Log debug messages about native virtual input devices.
- * Enable this via "adb shell setprop log.tag.InputController DEBUG"
- */
-static bool isDebug() {
-    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
-}
+using android::base::unique_fd;
 
 namespace android {
 
+static constexpr jlong INVALID_PTR = 0;
+
 enum class DeviceType {
     KEYBOARD,
     MOUSE,
@@ -49,176 +46,18 @@
     DPAD,
 };
 
-enum class UinputAction {
-    RELEASE = 0,
-    PRESS = 1,
-    MOVE = 2,
-    CANCEL = 3,
-};
-
-static std::map<int, UinputAction> BUTTON_ACTION_MAPPING = {
-        {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS},
-        {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE},
-};
-
-static std::map<int, UinputAction> KEY_ACTION_MAPPING = {
-        {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS},
-        {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE},
-};
-
-static std::map<int, UinputAction> TOUCH_ACTION_MAPPING = {
-        {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS},
-        {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE},
-        {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE},
-        {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL},
-};
-
-// Button code mapping from https://source.android.com/devices/input/touch-devices
-static std::map<int, int> BUTTON_CODE_MAPPING = {
-        {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT},    {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT},
-        {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE}, {AMOTION_EVENT_BUTTON_BACK, BTN_BACK},
-        {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD},
-};
-
-// Tool type mapping from https://source.android.com/devices/input/touch-devices
-static std::map<int, int> TOOL_TYPE_MAPPING = {
-        {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER},
-        {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM},
-};
-
-// Dpad keycode mapping from https://source.android.com/devices/input/keyboard-devices
-static std::map<int, int> DPAD_KEY_CODE_MAPPING = {
-        {AKEYCODE_DPAD_DOWN, KEY_DOWN},     {AKEYCODE_DPAD_UP, KEY_UP},
-        {AKEYCODE_DPAD_LEFT, KEY_LEFT},     {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
-        {AKEYCODE_DPAD_CENTER, KEY_SELECT}, {AKEYCODE_BACK, KEY_BACK},
-};
-
-// Keycode mapping from https://source.android.com/devices/input/keyboard-devices
-static std::map<int, int> KEY_CODE_MAPPING = {
-        {AKEYCODE_0, KEY_0},
-        {AKEYCODE_1, KEY_1},
-        {AKEYCODE_2, KEY_2},
-        {AKEYCODE_3, KEY_3},
-        {AKEYCODE_4, KEY_4},
-        {AKEYCODE_5, KEY_5},
-        {AKEYCODE_6, KEY_6},
-        {AKEYCODE_7, KEY_7},
-        {AKEYCODE_8, KEY_8},
-        {AKEYCODE_9, KEY_9},
-        {AKEYCODE_A, KEY_A},
-        {AKEYCODE_B, KEY_B},
-        {AKEYCODE_C, KEY_C},
-        {AKEYCODE_D, KEY_D},
-        {AKEYCODE_E, KEY_E},
-        {AKEYCODE_F, KEY_F},
-        {AKEYCODE_G, KEY_G},
-        {AKEYCODE_H, KEY_H},
-        {AKEYCODE_I, KEY_I},
-        {AKEYCODE_J, KEY_J},
-        {AKEYCODE_K, KEY_K},
-        {AKEYCODE_L, KEY_L},
-        {AKEYCODE_M, KEY_M},
-        {AKEYCODE_N, KEY_N},
-        {AKEYCODE_O, KEY_O},
-        {AKEYCODE_P, KEY_P},
-        {AKEYCODE_Q, KEY_Q},
-        {AKEYCODE_R, KEY_R},
-        {AKEYCODE_S, KEY_S},
-        {AKEYCODE_T, KEY_T},
-        {AKEYCODE_U, KEY_U},
-        {AKEYCODE_V, KEY_V},
-        {AKEYCODE_W, KEY_W},
-        {AKEYCODE_X, KEY_X},
-        {AKEYCODE_Y, KEY_Y},
-        {AKEYCODE_Z, KEY_Z},
-        {AKEYCODE_GRAVE, KEY_GRAVE},
-        {AKEYCODE_MINUS, KEY_MINUS},
-        {AKEYCODE_EQUALS, KEY_EQUAL},
-        {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE},
-        {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE},
-        {AKEYCODE_BACKSLASH, KEY_BACKSLASH},
-        {AKEYCODE_SEMICOLON, KEY_SEMICOLON},
-        {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE},
-        {AKEYCODE_COMMA, KEY_COMMA},
-        {AKEYCODE_PERIOD, KEY_DOT},
-        {AKEYCODE_SLASH, KEY_SLASH},
-        {AKEYCODE_ALT_LEFT, KEY_LEFTALT},
-        {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT},
-        {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL},
-        {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL},
-        {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT},
-        {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT},
-        {AKEYCODE_META_LEFT, KEY_LEFTMETA},
-        {AKEYCODE_META_RIGHT, KEY_RIGHTMETA},
-        {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK},
-        {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK},
-        {AKEYCODE_NUM_LOCK, KEY_NUMLOCK},
-        {AKEYCODE_ENTER, KEY_ENTER},
-        {AKEYCODE_TAB, KEY_TAB},
-        {AKEYCODE_SPACE, KEY_SPACE},
-        {AKEYCODE_DPAD_DOWN, KEY_DOWN},
-        {AKEYCODE_DPAD_UP, KEY_UP},
-        {AKEYCODE_DPAD_LEFT, KEY_LEFT},
-        {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
-        {AKEYCODE_MOVE_END, KEY_END},
-        {AKEYCODE_MOVE_HOME, KEY_HOME},
-        {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN},
-        {AKEYCODE_PAGE_UP, KEY_PAGEUP},
-        {AKEYCODE_DEL, KEY_BACKSPACE},
-        {AKEYCODE_FORWARD_DEL, KEY_DELETE},
-        {AKEYCODE_INSERT, KEY_INSERT},
-        {AKEYCODE_ESCAPE, KEY_ESC},
-        {AKEYCODE_BREAK, KEY_PAUSE},
-        {AKEYCODE_F1, KEY_F1},
-        {AKEYCODE_F2, KEY_F2},
-        {AKEYCODE_F3, KEY_F3},
-        {AKEYCODE_F4, KEY_F4},
-        {AKEYCODE_F5, KEY_F5},
-        {AKEYCODE_F6, KEY_F6},
-        {AKEYCODE_F7, KEY_F7},
-        {AKEYCODE_F8, KEY_F8},
-        {AKEYCODE_F9, KEY_F9},
-        {AKEYCODE_F10, KEY_F10},
-        {AKEYCODE_F11, KEY_F11},
-        {AKEYCODE_F12, KEY_F12},
-        {AKEYCODE_BACK, KEY_BACK},
-        {AKEYCODE_FORWARD, KEY_FORWARD},
-        {AKEYCODE_NUMPAD_1, KEY_KP1},
-        {AKEYCODE_NUMPAD_2, KEY_KP2},
-        {AKEYCODE_NUMPAD_3, KEY_KP3},
-        {AKEYCODE_NUMPAD_4, KEY_KP4},
-        {AKEYCODE_NUMPAD_5, KEY_KP5},
-        {AKEYCODE_NUMPAD_6, KEY_KP6},
-        {AKEYCODE_NUMPAD_7, KEY_KP7},
-        {AKEYCODE_NUMPAD_8, KEY_KP8},
-        {AKEYCODE_NUMPAD_9, KEY_KP9},
-        {AKEYCODE_NUMPAD_0, KEY_KP0},
-        {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS},
-        {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS},
-        {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK},
-        {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH},
-        {AKEYCODE_NUMPAD_DOT, KEY_KPDOT},
-        {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
-        {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
-        {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
-};
-
-/*
- * Map from the uinput touchscreen fd to the pointers present in the previous touch events that
- * hasn't been lifted.
- * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual
- * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id to go
- * up to MAX_POINTERS_ID.
- */
-static std::map<int32_t, std::bitset<MAX_POINTERS>> unreleasedTouches;
+static unique_fd invalidFd() {
+    return unique_fd(-1);
+}
 
 /** Creates a new uinput device and assigns a file descriptor. */
-static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys,
-                      DeviceType deviceType, jint screenHeight, jint screenWidth) {
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
+static unique_fd openUinput(const char* readableName, jint vendorId, jint productId,
+                            const char* phys, DeviceType deviceType, jint screenHeight,
+                            jint screenWidth) {
+    unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
     if (fd < 0) {
         ALOGE("Error creating uinput device: %s", strerror(errno));
-        return -errno;
+        return invalidFd();
     }
 
     ioctl(fd, UI_SET_PHYS, phys);
@@ -227,12 +66,12 @@
     ioctl(fd, UI_SET_EVBIT, EV_SYN);
     switch (deviceType) {
         case DeviceType::DPAD:
-            for (const auto& [_, keyCode] : DPAD_KEY_CODE_MAPPING) {
+            for (const auto& [_, keyCode] : VirtualDpad::DPAD_KEY_CODE_MAPPING) {
                 ioctl(fd, UI_SET_KEYBIT, keyCode);
             }
             break;
         case DeviceType::KEYBOARD:
-            for (const auto& [_, keyCode] : KEY_CODE_MAPPING) {
+            for (const auto& [_, keyCode] : VirtualKeyboard::KEY_CODE_MAPPING) {
                 ioctl(fd, UI_SET_KEYBIT, keyCode);
             }
             break;
@@ -277,7 +116,7 @@
             xAbsSetup.absinfo.minimum = 0;
             if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
                 ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno));
-                return -errno;
+                return invalidFd();
             }
             uinput_abs_setup yAbsSetup;
             yAbsSetup.code = ABS_MT_POSITION_Y;
@@ -285,7 +124,7 @@
             yAbsSetup.absinfo.minimum = 0;
             if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
                 ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno));
-                return -errno;
+                return invalidFd();
             }
             uinput_abs_setup majorAbsSetup;
             majorAbsSetup.code = ABS_MT_TOUCH_MAJOR;
@@ -293,7 +132,7 @@
             majorAbsSetup.absinfo.minimum = 0;
             if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) {
                 ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno));
-                return -errno;
+                return invalidFd();
             }
             uinput_abs_setup pressureAbsSetup;
             pressureAbsSetup.code = ABS_MT_PRESSURE;
@@ -301,7 +140,7 @@
             pressureAbsSetup.absinfo.minimum = 0;
             if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
                 ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
-                return -errno;
+                return invalidFd();
             }
             uinput_abs_setup slotAbsSetup;
             slotAbsSetup.code = ABS_MT_SLOT;
@@ -309,12 +148,12 @@
             slotAbsSetup.absinfo.minimum = 0;
             if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) {
                 ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno));
-                return -errno;
+                return invalidFd();
             }
         }
         if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
             ALOGE("Error creating uinput device: %s", strerror(errno));
-            return -errno;
+            return invalidFd();
         }
     } else {
         // UI_DEV_SETUP was not introduced until version 5. Try setting up manually.
@@ -338,255 +177,118 @@
         }
         if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) {
             ALOGE("Error creating uinput device: %s", strerror(errno));
-            return -errno;
+            return invalidFd();
         }
     }
 
     if (ioctl(fd, UI_DEV_CREATE) != 0) {
         ALOGE("Error creating uinput device: %s", strerror(errno));
-        return -errno;
+        return invalidFd();
     }
 
-    return fd.release();
+    return fd;
 }
 
-static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, jstring phys,
-                         DeviceType deviceType, int screenHeight, int screenWidth) {
+static unique_fd openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+                               jstring phys, DeviceType deviceType, int screenHeight,
+                               int screenWidth) {
     ScopedUtfChars readableName(env, name);
     ScopedUtfChars readablePhys(env, phys);
     return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType,
                       screenHeight, screenWidth);
 }
 
-static int nativeOpenUinputDpad(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                jint productId, jstring phys) {
-    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::DPAD,
-                         /* screenHeight */ 0, /* screenWidth */ 0);
+static jlong nativeOpenUinputDpad(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                  jint productId, jstring phys) {
+    auto fd = openUinputJni(env, name, vendorId, productId, phys, DeviceType::DPAD,
+                            /* screenHeight= */ 0, /* screenWidth= */ 0);
+    return fd.ok() ? reinterpret_cast<jlong>(new VirtualDpad(std::move(fd))) : INVALID_PTR;
 }
 
-static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                    jint productId, jstring phys) {
-    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
-                         /* screenHeight */ 0, /* screenWidth */ 0);
+static jlong nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                      jint productId, jstring phys) {
+    auto fd = openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
+                            /* screenHeight= */ 0, /* screenWidth= */ 0);
+    return fd.ok() ? reinterpret_cast<jlong>(new VirtualKeyboard(std::move(fd))) : INVALID_PTR;
 }
 
-static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                 jint productId, jstring phys) {
-    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE,
-                         /* screenHeight */ 0, /* screenWidth */ 0);
+static jlong nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                   jint productId, jstring phys) {
+    auto fd = openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE,
+                            /* screenHeight= */ 0, /* screenWidth= */ 0);
+    return fd.ok() ? reinterpret_cast<jlong>(new VirtualMouse(std::move(fd))) : INVALID_PTR;
 }
 
-static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
-                                       jint productId, jstring phys, jint height, jint width) {
-    return openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height,
-                         width);
+static jlong nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                         jint productId, jstring phys, jint height, jint width) {
+    auto fd = openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height,
+                            width);
+    return fd.ok() ? reinterpret_cast<jlong>(new VirtualTouchscreen(std::move(fd))) : INVALID_PTR;
 }
 
-static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
-    ioctl(fd, UI_DEV_DESTROY);
-    if (auto touchesOnFd = unreleasedTouches.find(fd); touchesOnFd != unreleasedTouches.end()) {
-        const size_t remainingPointers = touchesOnFd->second.size();
-        unreleasedTouches.erase(touchesOnFd);
-        ALOGW_IF(remainingPointers > 0, "Closing touchscreen %d, erased %zu unreleased pointers.",
-                 fd, remainingPointers);
-    }
-    return close(fd);
+static void nativeCloseUinput(JNIEnv* env, jobject thiz, jlong ptr) {
+    VirtualInputDevice* virtualInputDevice = reinterpret_cast<VirtualInputDevice*>(ptr);
+    delete virtualInputDevice;
 }
 
-static bool writeInputEvent(int fd, uint16_t type, uint16_t code, int32_t value) {
-    struct input_event ev = {.type = type, .code = code, .value = value};
-    return TEMP_FAILURE_RETRY(write(fd, &ev, sizeof(struct input_event))) == sizeof(ev);
-}
-
-static bool writeKeyEvent(jint fd, jint androidKeyCode, jint action,
-                          const std::map<int, int>& keyCodeMapping) {
-    auto keyCodeIterator = keyCodeMapping.find(androidKeyCode);
-    if (keyCodeIterator == keyCodeMapping.end()) {
-        ALOGE("Unsupported native keycode for androidKeyCode %d", androidKeyCode);
-        return false;
-    }
-    auto actionIterator = KEY_ACTION_MAPPING.find(action);
-    if (actionIterator == KEY_ACTION_MAPPING.end()) {
-        return false;
-    }
-    if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(keyCodeIterator->second),
-                         static_cast<int32_t>(actionIterator->second))) {
-        return false;
-    }
-    if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) {
-        return false;
-    }
-    return true;
-}
-
-static bool nativeWriteDpadKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode,
+// Native methods for VirtualDpad
+static bool nativeWriteDpadKeyEvent(JNIEnv* env, jobject thiz, jlong ptr, jint androidKeyCode,
                                     jint action) {
-    return writeKeyEvent(fd, androidKeyCode, action, DPAD_KEY_CODE_MAPPING);
+    VirtualDpad* virtualDpad = reinterpret_cast<VirtualDpad*>(ptr);
+    return virtualDpad->writeDpadKeyEvent(androidKeyCode, action);
 }
 
-static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode,
+// Native methods for VirtualKeyboard
+static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jlong ptr, jint androidKeyCode,
                                 jint action) {
-    return writeKeyEvent(fd, androidKeyCode, action, KEY_CODE_MAPPING);
+    VirtualKeyboard* virtualKeyboard = reinterpret_cast<VirtualKeyboard*>(ptr);
+    return virtualKeyboard->writeKeyEvent(androidKeyCode, action);
 }
 
-static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jint fd, jint buttonCode,
+// Native methods for VirtualTouchscreen
+static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jlong ptr, jint pointerId,
+                                  jint toolType, jint action, jfloat locationX, jfloat locationY,
+                                  jfloat pressure, jfloat majorAxisSize) {
+    VirtualTouchscreen* virtualTouchscreen = reinterpret_cast<VirtualTouchscreen*>(ptr);
+    return virtualTouchscreen->writeTouchEvent(pointerId, toolType, action, locationX, locationY,
+                                               pressure, majorAxisSize);
+}
+
+// Native methods for VirtualMouse
+static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jlong ptr, jint buttonCode,
                                    jint action) {
-    auto buttonCodeIterator = BUTTON_CODE_MAPPING.find(buttonCode);
-    if (buttonCodeIterator == BUTTON_CODE_MAPPING.end()) {
-        return false;
-    }
-    auto actionIterator = BUTTON_ACTION_MAPPING.find(action);
-    if (actionIterator == BUTTON_ACTION_MAPPING.end()) {
-        return false;
-    }
-    if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(buttonCodeIterator->second),
-                         static_cast<int32_t>(actionIterator->second))) {
-        return false;
-    }
-    if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) {
-        return false;
-    }
-    return true;
+    VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr);
+    return virtualMouse->writeButtonEvent(buttonCode, action);
 }
 
-static bool handleTouchUp(int fd, int pointerId) {
-    if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1))) {
-        return false;
-    }
-    auto touchesOnFd = unreleasedTouches.find(fd);
-    if (touchesOnFd == unreleasedTouches.end()) {
-        ALOGE("PointerId %d action UP received with no prior events on touchscreen %d.", pointerId,
-              fd);
-        return false;
-    }
-    ALOGD_IF(isDebug(), "Unreleased touches found for touchscreen %d in the map", fd);
-
-    // When a pointer is no longer in touch, remove the pointer id from the corresponding
-    // entry in the unreleased touches map.
-    if (pointerId < 0 || pointerId >= MAX_POINTERS) {
-        ALOGE("Virtual touch event has invalid pointer id %d; value must be between 0 and %zu",
-              pointerId, MAX_POINTERS - 1);
-        return false;
-    }
-    if (!touchesOnFd->second.test(pointerId)) {
-        ALOGE("PointerId %d action UP received with no prior action DOWN on touchscreen %d.",
-              pointerId, fd);
-        return false;
-    }
-    touchesOnFd->second.reset(pointerId);
-    ALOGD_IF(isDebug(), "Pointer %d erased from the touchscreen %d", pointerId, fd);
-
-    // Only sends the BTN UP event when there's no pointers on the touchscreen.
-    if (touchesOnFd->second.none()) {
-        unreleasedTouches.erase(touchesOnFd);
-        if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE))) {
-            return false;
-        }
-        ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", fd);
-    }
-    return true;
-}
-
-static bool handleTouchDown(int fd, int pointerId) {
-    // When a new pointer is down on the touchscreen, add the pointer id in the corresponding
-    // entry in the unreleased touches map.
-    auto touchesOnFd = unreleasedTouches.find(fd);
-    if (touchesOnFd == unreleasedTouches.end()) {
-        // Only sends the BTN Down event when the first pointer on the touchscreen is down.
-        if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS))) {
-            return false;
-        }
-        touchesOnFd = unreleasedTouches.insert({fd, {}}).first;
-        ALOGD_IF(isDebug(), "New touchscreen with fd %d added in the unreleased touches map.", fd);
-    }
-    if (touchesOnFd->second.test(pointerId)) {
-        ALOGE("Repetitive action DOWN event received on a pointer %d that is already down.",
-              pointerId);
-        return false;
-    }
-    touchesOnFd->second.set(pointerId);
-    ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, fd);
-    if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId))) {
-        return false;
-    }
-    return true;
-}
-
-static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jint fd, jint pointerId, jint toolType,
-                                  jint action, jfloat locationX, jfloat locationY, jfloat pressure,
-                                  jfloat majorAxisSize) {
-    if (!writeInputEvent(fd, EV_ABS, ABS_MT_SLOT, pointerId)) {
-        return false;
-    }
-    auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType);
-    if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) {
-        return false;
-    }
-    if (toolType != -1) {
-        if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOOL_TYPE,
-                             static_cast<int32_t>(toolTypeIterator->second))) {
-            return false;
-        }
-    }
-    auto actionIterator = TOUCH_ACTION_MAPPING.find(action);
-    if (actionIterator == TOUCH_ACTION_MAPPING.end()) {
-        return false;
-    }
-    UinputAction uinputAction = actionIterator->second;
-    if (uinputAction == UinputAction::PRESS && !handleTouchDown(fd, pointerId)) {
-        return false;
-    } else if (uinputAction == UinputAction::RELEASE && !handleTouchUp(fd, pointerId)) {
-        return false;
-    }
-    if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_X, locationX)) {
-        return false;
-    }
-    if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_Y, locationY)) {
-        return false;
-    }
-    if (!isnan(pressure)) {
-        if (!writeInputEvent(fd, EV_ABS, ABS_MT_PRESSURE, pressure)) {
-            return false;
-        }
-    }
-    if (!isnan(majorAxisSize)) {
-        if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) {
-            return false;
-        }
-    }
-    return writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
-}
-
-static bool nativeWriteRelativeEvent(JNIEnv* env, jobject thiz, jint fd, jfloat relativeX,
+static bool nativeWriteRelativeEvent(JNIEnv* env, jobject thiz, jlong ptr, jfloat relativeX,
                                      jfloat relativeY) {
-    return writeInputEvent(fd, EV_REL, REL_X, relativeX) &&
-            writeInputEvent(fd, EV_REL, REL_Y, relativeY) &&
-            writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+    VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr);
+    return virtualMouse->writeRelativeEvent(relativeX, relativeY);
 }
 
-static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jint fd, jfloat xAxisMovement,
+static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jlong ptr, jfloat xAxisMovement,
                                    jfloat yAxisMovement) {
-    return writeInputEvent(fd, EV_REL, REL_HWHEEL, xAxisMovement) &&
-            writeInputEvent(fd, EV_REL, REL_WHEEL, yAxisMovement) &&
-            writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+    VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr);
+    return virtualMouse->writeScrollEvent(xAxisMovement, yAxisMovement);
 }
 
 static JNINativeMethod methods[] = {
-        {"nativeOpenUinputDpad", "(Ljava/lang/String;IILjava/lang/String;)I",
+        {"nativeOpenUinputDpad", "(Ljava/lang/String;IILjava/lang/String;)J",
          (void*)nativeOpenUinputDpad},
-        {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I",
+        {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)J",
          (void*)nativeOpenUinputKeyboard},
-        {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I",
+        {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)J",
          (void*)nativeOpenUinputMouse},
-        {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I",
+        {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)J",
          (void*)nativeOpenUinputTouchscreen},
-        {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
-        {"nativeWriteDpadKeyEvent", "(III)Z", (void*)nativeWriteDpadKeyEvent},
-        {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
-        {"nativeWriteButtonEvent", "(III)Z", (void*)nativeWriteButtonEvent},
-        {"nativeWriteTouchEvent", "(IIIIFFFF)Z", (void*)nativeWriteTouchEvent},
-        {"nativeWriteRelativeEvent", "(IFF)Z", (void*)nativeWriteRelativeEvent},
-        {"nativeWriteScrollEvent", "(IFF)Z", (void*)nativeWriteScrollEvent},
+        {"nativeCloseUinput", "(J)V", (void*)nativeCloseUinput},
+        {"nativeWriteDpadKeyEvent", "(JII)Z", (void*)nativeWriteDpadKeyEvent},
+        {"nativeWriteKeyEvent", "(JII)Z", (void*)nativeWriteKeyEvent},
+        {"nativeWriteButtonEvent", "(JII)Z", (void*)nativeWriteButtonEvent},
+        {"nativeWriteTouchEvent", "(JIIIFFFF)Z", (void*)nativeWriteTouchEvent},
+        {"nativeWriteRelativeEvent", "(JFF)Z", (void*)nativeWriteRelativeEvent},
+        {"nativeWriteScrollEvent", "(JFF)Z", (void*)nativeWriteScrollEvent},
 };
 
 int register_android_server_companion_virtual_InputController(JNIEnv* env) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 852e773..bfcb4c7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -22466,7 +22466,7 @@
                 } else {
                     // If the permission maps to no policy (null) this means that any active admin
                     // has permission.
-                    return getActiveAdminForUidLocked(null, caller.getUid()) != null;
+                    return isCallerActiveAdminOrDelegate(caller, null);
                 }
             } catch (SecurityException e) {
                 // A security exception means there is not an active admin with permission and
@@ -22505,23 +22505,25 @@
 
     private EnforcingAdmin getEnforcingAdminForCaller(@Nullable ComponentName who,
             String callerPackageName) {
-        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
         int userId = caller.getUserId();
         ActiveAdmin admin;
-        synchronized (getLockObject()) {
-            admin = getActiveAdminUncheckedLocked(who, userId);
+        if (isDeviceOwner(caller) || isProfileOwner(caller) || isCallerDelegate(caller)) {
+            ComponentName component;
+            synchronized (getLockObject()) {
+                if (who != null) {
+                    admin = getActiveAdminUncheckedLocked(who, userId);
+                    component = who;
+                } else {
+                    admin = getDeviceOrProfileOwnerAdminLocked(userId);
+                    component = admin.info.getComponent();
+                }
+            }
+            return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId, admin);
         }
-        if (isDeviceOwner(caller) || isProfileOwner(caller)) {
-            return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
-        }
-        if (isCallerDelegate(caller)) {
-            ComponentName profileOwner = mOwners.getProfileOwnerComponent(caller.getUserId());
-            ComponentName dpc = profileOwner != null ? profileOwner :
-                    mOwners.getDeviceOwnerComponent();
-            ActiveAdmin dpcAdmin = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
-            return EnforcingAdmin.createEnterpriseEnforcingAdmin(dpc, userId, dpcAdmin);
-        }
-        if (getActiveAdminUncheckedLocked(who, userId) != null) {
+        // Check for non-DPC active admins.
+        admin = getActiveAdminForCaller(who, caller);
+        if (admin != null) {
             return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
         }
         if (admin == null) {
@@ -23136,6 +23138,26 @@
         });
     }
 
+    private ActiveAdmin getActiveAdminForCaller(@Nullable ComponentName who,
+            CallerIdentity caller) {
+        synchronized (getLockObject()) {
+            if (who != null) {
+                return getActiveAdminUncheckedLocked(who, caller.getUserId());
+            }
+            return mInjector.binderWithCleanCallingIdentity(() -> {
+                List<ComponentName> activeAdmins = getActiveAdmins(caller.getUserId());
+                if (activeAdmins != null) {
+                    for (ComponentName admin : activeAdmins) {
+                        if (admin.getPackageName().equals(caller.getPackageName())) {
+                            return getActiveAdminUncheckedLocked(admin, caller.getUserId());
+                        }
+                    }
+                }
+                return null;
+            });
+        }
+    }
+
     // TODO(b/266808047): This will return false for DeviceAdmins not targetting U, which is
     //  inconsistent with the migration logic that allows migration with old DeviceAdmins.
     private boolean canAddActiveAdminIfPolicyEngineEnabled(String packageName, int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 3fd0e07..f8cfdf1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -17,9 +17,12 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+
 import static com.android.server.am.ActivityManagerService.Injector;
 import static com.android.server.am.CachedAppOptimizer.compactActionIntToAction;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
@@ -34,27 +37,30 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
+
 import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.android.server.ExtendedMockitoTestCase;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.appop.AppOpsService;
 import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
 
 /**
  * Tests for {@link CachedAppOptimizer}.
@@ -63,10 +69,7 @@
  * atest FrameworksMockingServicesTests:CachedAppOptimizerTest
  */
 @Presubmit
-@RunWith(MockitoJUnitRunner.class)
-@Ignore("TODO(b/226641572): this test is broken and it cannot use ExtendedMockitoTestCase as it "
-        + "uses TestableDeviceConfigRule, which creates its own mockito session")
-public final class CachedAppOptimizerTest {
+public final class CachedAppOptimizerTest extends ExtendedMockitoTestCase {
 
     private ServiceThread mThread;
 
@@ -84,16 +87,21 @@
     @Mock
     private PackageManagerInternal mPackageManagerInt;
 
-    @Rule
-    public final TestableDeviceConfig.TestableDeviceConfigRule
-            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+    private final TestableDeviceConfig mDeviceConfig = new TestableDeviceConfig();
+
     @Rule
     public final ApplicationExitInfoTest.ServiceThreadRule
             mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
 
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        mDeviceConfig.setUpMockedClasses(builder);
+    }
+
     @Before
     public void setUp() {
         System.loadLibrary("mockingservicestestjni");
+        mDeviceConfig.setUpMockBehaviors();
         mHandlerThread = new HandlerThread("");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -124,6 +132,7 @@
         mHandlerThread.quit();
         mThread.quit();
         mCountDown = null;
+        mDeviceConfig.tearDown();
     }
 
     private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName,
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 89a5b12..8994a48 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -133,6 +133,7 @@
     private static final int TEST_USER_ID1 = 101;
     private static final int TEST_USER_ID2 = 102;
     private static final int TEST_USER_ID3 = 103;
+    private static final int SYSTEM_USER_ID = UserHandle.SYSTEM.getIdentifier();
     private static final int NONEXIST_USER_ID = 2;
     private static final int TEST_PRE_CREATED_USER_ID = 103;
 
@@ -231,6 +232,31 @@
     }
 
     @Test
+    public void testStartUser_sendsNoBroadcastsForSystemUserInNonHeadlessMode() {
+        setUpUser(SYSTEM_USER_ID, UserInfo.FLAG_SYSTEM, /* preCreated= */ false,
+                UserManager.USER_TYPE_FULL_SYSTEM);
+        mockIsHeadlessSystemUserMode(false);
+
+        mUserController.startUser(SYSTEM_USER_ID, USER_START_MODE_FOREGROUND);
+
+        assertWithMessage("Broadcasts for starting the system user in non-headless mode")
+                .that(mInjector.mSentIntents).isEmpty();
+    }
+
+    @Test
+    public void testStartUser_sendsBroadcastsForSystemUserInHeadlessMode() {
+        setUpUser(SYSTEM_USER_ID, UserInfo.FLAG_SYSTEM, /* preCreated= */ false,
+                UserManager.USER_TYPE_SYSTEM_HEADLESS);
+        mockIsHeadlessSystemUserMode(true);
+
+        mUserController.startUser(SYSTEM_USER_ID, USER_START_MODE_FOREGROUND);
+
+        assertWithMessage("Broadcasts for starting the system user in headless mode")
+                .that(getActions(mInjector.mSentIntents)).containsExactly(
+                        Intent.ACTION_USER_STARTED, Intent.ACTION_USER_STARTING);
+    }
+
+    @Test
     public void testStartUser_displayAssignmentFailed() {
         doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
                 .when(mInjector.mUserManagerInternalMock)
@@ -999,6 +1025,10 @@
         }
     }
 
+    private void mockIsHeadlessSystemUserMode(boolean value) {
+        when(mInjector.isHeadlessSystemUserMode()).thenReturn(value);
+    }
+
     private void mockIsUsersOnSecondaryDisplaysEnabled(boolean value) {
         when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
     }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index 51bd5b0f..3cc6b01 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -79,7 +79,7 @@
         InputManager.resetInstance(mIInputManagerMock);
     }
 
-    private Void handleNativeOpenInputDevice(InvocationOnMock inv) {
+    private long handleNativeOpenInputDevice(InvocationOnMock inv) {
         Objects.requireNonNull(mDevicesChangedListener,
                 "InputController did not register an InputDevicesChangedListener.");
 
@@ -101,6 +101,7 @@
         }
         // Process the device added notification.
         mTestableLooper.processAllMessages();
-        return null;
+        // Return a placeholder pointer to the native input device.
+        return 1L;
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ef3b007..f08d0f5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -177,7 +177,6 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.permission.PermissionCheckerManager;
 import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
@@ -10254,7 +10253,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionCheckerManager.PERMISSION_HARD_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED,
                 /* isSticky= */ true);
     }
 
@@ -10263,7 +10262,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionCheckerManager.PERMISSION_SOFT_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED,
                 /* isSticky= */ true);
     }
 
@@ -10272,7 +10271,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
-                /* permissionState= */ PermissionCheckerManager.PERMISSION_GRANTED,
+                /* permissionState= */ PermissionManager.PERMISSION_GRANTED,
                 /* isSticky= */ false);
     }
 
@@ -10281,7 +10280,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionCheckerManager.PERMISSION_HARD_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED,
                 /* isSticky= */ true);
     }
 
@@ -10290,7 +10289,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionCheckerManager.PERMISSION_SOFT_DENIED,
+                /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED,
                 /* isSticky= */ true);
     }
 
@@ -10299,7 +10298,7 @@
             throws Exception {
 
         verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
-                /* permissionState= */ PermissionCheckerManager.PERMISSION_GRANTED,
+                /* permissionState= */ PermissionManager.PERMISSION_GRANTED,
                 /* isSticky= */ true);
     }
 
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 c9f758c..e9aca56 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -279,7 +279,8 @@
     public void testTranslucentActivitiesWhenUnfolding() {
         mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
         setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(
+                true /* ignoreOrientationRequest */);
         mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
                 1.0f /*letterboxVerticalPositionMultiplier*/);
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
@@ -290,18 +291,23 @@
                 .build();
         doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
+        assertEquals(translucentActivity.getBounds(), mActivity.getBounds());
 
         mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         spyOn(mActivity);
 
         // Halffold
-        setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */);
+        setFoldablePosture(translucentActivity, true /* isHalfFolded */,
+                false /* isTabletop */);
         verify(mActivity).recomputeConfiguration();
+        assertEquals(translucentActivity.getBounds(), mActivity.getBounds());
         clearInvocations(mActivity);
 
         // Unfold
-        setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */);
+        setFoldablePosture(translucentActivity, false /* isHalfFolded */,
+                false /* isTabletop */);
         verify(mActivity).recomputeConfiguration();
+        assertEquals(translucentActivity.getBounds(), mActivity.getBounds());
     }
 
     @Test
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 05ee2f2..3f14217 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1018,6 +1018,10 @@
 
     @Test
     public void testDisplayRotationChange() {
+        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        spyOn(displayPolicy);
+        // Simulate gesture navigation (non-movable) so it is not seamless.
+        doReturn(false).when(displayPolicy).navigationBarCanMove();
         final Task task = createActivityRecord(mDisplayContent).getTask();
         final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
         final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
@@ -1072,7 +1076,8 @@
 
         // Navigation bar finishes drawing after the start transaction, so its fade-in animation
         // can execute directly.
-        asyncRotationController.handleFinishDrawing(navBar, mMockT);
+        navBar.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        asyncRotationController.updateTargetWindows();
         assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
         assertNull(mDisplayContent.getAsyncRotationController());
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index fd54293..1a76295 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -378,7 +378,7 @@
         @Override
         public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
                 @NonNull Identity originatorIdentity, IBinder client,
-                @NonNull ModuleProperties moduleProperties) {
+                ModuleProperties moduleProperties) {
             Objects.requireNonNull(originatorIdentity);
             boolean forHotwordDetectionService;
             synchronized (VoiceInteractionManagerServiceStub.this) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index d75e573..b418a02 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2241,46 +2241,46 @@
     }
 
     /**
-     * Get an array of subscription ids for specified logical SIM slot Index.
+     * Get an array of subscription ids for the specified logical SIM slot Index. The maximum size
+     * of the array is 1. This API was mistakenly designed to return multiple subscription ids,
+     * which is not possible in the current Android telephony architecture.
      *
      * @param slotIndex The logical SIM slot index.
      *
-     * @return subscription Ids or {@code null} if the given slot index is not valid or there are
-     * no active subscription in the slot. In the implementation today, there will be no more
-     * than one subscriptions per logical SIM slot.
+     * @return Subscription id of the active subscription on the specified logical SIM slot index.
+     * If SIM is absent on the slot, a single element array of {@link #INVALID_SUBSCRIPTION_ID} will
+     * be returned. {@code null} if the provided {@code slotIndex} is not valid.
      *
      * @deprecated Use {@link #getSubscriptionId(int)} instead.
      */
     @Deprecated
     @Nullable
     public int[] getSubscriptionIds(int slotIndex) {
-        int subId = getSubscriptionId(slotIndex);
-        if (!isValidSubscriptionId(subId)) {
+        if (!isValidSlotIndex(slotIndex)) {
             return null;
         }
         return new int[]{getSubscriptionId(slotIndex)};
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * Get an array of subscription ids for the specified logical SIM slot Index. The maximum size
+     * of the array is 1. This API was mistakenly designed to return multiple subscription ids,
+     * which is not possible in the current Android telephony architecture.
+     *
+     * @param slotIndex The logical SIM slot index.
+     *
+     * @return Subscription id of the active subscription on the specified logical SIM slot index.
+     * If SIM is absent on the slot, a single element array of {@link #INVALID_SUBSCRIPTION_ID} will
+     * be returned. {@code null} if the provided {@code slotIndex} is not valid.
+     *
+     * @deprecated Use {@link #getSubscriptionId(int)} instead.
+     * @hide
+     */
     public static int[] getSubId(int slotIndex) {
         if (!isValidSlotIndex(slotIndex)) {
-            logd("[getSubId]- fail");
             return null;
         }
-
-        int[] subId = null;
-
-        try {
-            ISub iSub = TelephonyManager.getSubscriptionService();
-            if (iSub != null) {
-                subId = iSub.getSubIds(slotIndex);
-            }
-        } catch (RemoteException ex) {
-            // ignore it
-        }
-
-        return subId;
+        return new int[]{getSubscriptionId(slotIndex)};
     }
 
     /**
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
index abf2b55..c15374a 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
@@ -25,10 +25,10 @@
  */
 oneway interface ISatelliteDatagramCallback {
     /**
-     * Called when datagrams are received from satellite.
+     * Called when there is an incoming datagram to be received from satellite.
      *
      * @param datagramId An id that uniquely identifies incoming datagram.
-     * @param datagram datagram received from satellite.
+     * @param datagram Datagram received from satellite.
      * @param pendingCount Number of datagrams yet to be received from satellite.
      * @param callback This callback will be used by datagram receiver app to send ack back to
      *                 Telephony. If the callback is not received within five minutes,
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
index 4478d0a..d3f1091 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
@@ -24,20 +24,20 @@
  */
 oneway interface ISatellitePositionUpdateCallback {
     /**
-     * Called when satellite datagram transfer state changes.
+     * 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.
      */
-    void onDatagramTransferStateUpdate(in int state, in int sendPendingCount,
+    void onDatagramTransferStateChanged(in int state, in int sendPendingCount,
             in int receivePendingCount, in int errorCode);
 
     /**
-     * Called when the satellite position changes.
+     * Called when the satellite position changed.
      *
      * @param pointingInfo The pointing info containing the satellite location.
      */
-    void onSatellitePositionUpdate(in PointingInfo pointingInfo);
+    void onSatellitePositionChanged(in PointingInfo pointingInfo);
 }
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
index 8d1e3c2..98221c9 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
@@ -33,5 +33,5 @@
      *
      * @param state The current satellite modem state.
      */
-    void onSatelliteModemStateChange(in int state);
+    void onSatelliteModemStateChanged(in int state);
 }
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index d6dd57a..a3c3f19 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -48,10 +48,10 @@
     /**
      * @hide
      */
-    public PointingInfo(float satelliteAzimuthDegress, float satelliteElevationDegress,
+    public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees,
             float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
-        mSatelliteAzimuthDegrees = satelliteAzimuthDegress;
-        mSatelliteElevationDegrees = satelliteElevationDegress;
+        mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
+        mSatelliteElevationDegrees = satelliteElevationDegrees;
         mAntennaAzimuthDegrees = antennaAzimuthDegrees;
         mAntennaPitchDegrees = antennaPitchDegrees;
         mAntennaRollDegrees = antennaRollDegrees;
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 74f6f57..889856b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -30,7 +30,7 @@
     /**
      * List of technologies supported by the satellite modem.
      */
-    private Set<Integer> mSupportedRadioTechnologies;
+    @NonNull @SatelliteManager.NTRadioTechnology private Set<Integer> mSupportedRadioTechnologies;
 
     /**
      * Whether satellite modem is always on.
@@ -53,7 +53,8 @@
      */
     public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
             boolean needsPointingToSatellite, boolean needsSeparateSimProfile) {
-        mSupportedRadioTechnologies = supportedRadioTechnologies;
+        mSupportedRadioTechnologies = supportedRadioTechnologies == null
+                ? new HashSet<>() : supportedRadioTechnologies;
         mIsAlwaysOn = isAlwaysOn;
         mNeedsPointingToSatellite = needsPointingToSatellite;
         mNeedsSeparateSimProfile = needsSeparateSimProfile;
@@ -126,7 +127,8 @@
     /**
      * @return The list of technologies supported by the satellite modem.
      */
-    @NonNull public Set<Integer> getSupportedRadioTechnologies() {
+    @NonNull @SatelliteManager.NTRadioTechnology public Set<Integer>
+            getSupportedRadioTechnologies() {
         return mSupportedRadioTechnologies;
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index 2c3884c..8ccc993 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -55,9 +55,9 @@
     }
 
     /**
-     * Called when there are incoming datagrams to be received.
+     * 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 datagram Datagram to be received over satellite.
      * @param pendingCount Number of datagrams yet to be received by the app.
      * @param callback This callback will be used by datagram receiver app to send ack back to
      *                 Telephony.
@@ -67,13 +67,13 @@
         // Base Implementation
     }
 
-    /**@hide*/
+    /** @hide */
     @NonNull
     public final ISatelliteDatagramCallback getBinder() {
         return mBinder;
     }
 
-    /**@hide*/
+    /** @hide */
     public void setExecutor(@NonNull Executor executor) {
         mBinder.setExecutor(executor);
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index dbc0ed9..3009bec 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -116,6 +116,13 @@
 
     /**
      * Bundle key to get the response from
+     * {@link #requestIsSatelliteDemoModeEnabled(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+    public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
+
+    /**
+     * Bundle key to get the response from
      * {@link #requestIsSatelliteSupported(Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -284,6 +291,39 @@
     public @interface SatelliteError {}
 
     /**
+     * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 0;
+    /**
+     * 3GPP 5G NR over Non-Terrestrial-Networks technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 1;
+    /**
+     * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 2;
+    /**
+     * Proprietary technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 3;
+    /**
+     * Unknown Non-Terrestrial radio technology. This generic radio technology should be used
+     * only when the radio technology cannot be mapped to other specific radio technologies.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = -1;
+
+    /** @hide */
+    @IntDef(prefix = "NT_RADIO_TECHNOLOGY_", value = {
+            NT_RADIO_TECHNOLOGY_NB_IOT_NTN,
+            NT_RADIO_TECHNOLOGY_NR_NTN,
+            NT_RADIO_TECHNOLOGY_EMTC_NTN,
+            NT_RADIO_TECHNOLOGY_PROPRIETARY,
+            NT_RADIO_TECHNOLOGY_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    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.
@@ -377,6 +417,97 @@
     }
 
     /**
+     * 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.
+     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     *
+     * @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 requestIsSatelliteDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Boolean, 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_DEMO_MODE_ENABLED)) {
+                                boolean isDemoModeEnabled =
+                                        resultData.getBoolean(KEY_DEMO_MODE_ENABLED);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(isDemoModeEnabled)));
+                            } else {
+                                loge("KEY_DEMO_MODE_ENABLED does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(
+                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestIsSatelliteDemoModeEnabled(mSubId, receiver);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("requestIsSatelliteDemoModeEnabled() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Request to get whether the satellite service is supported on the device.
      *
      * @param executor The executor on which the callback will be called.
@@ -513,6 +644,12 @@
      * must be sent before reporting any additional datagram transfer state changes.
      */
     public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED = 5;
+    /**
+     * The datagram transfer state is unknown. This generic datagram transfer state should be used
+     * only when the datagram transfer state cannot be mapped to other specific datagram transfer
+     * states.
+     */
+    public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1;
 
     /** @hide */
     @IntDef(prefix = {"SATELLITE_DATAGRAM_TRANSFER_STATE_"}, value = {
@@ -521,46 +658,70 @@
             SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
             SATELLITE_DATAGRAM_TRANSFER_STATE_RETRYING,
             SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS,
-            SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED
+            SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED,
+            SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN
     })
+    @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteDatagramTransferState {}
 
-    /* Satellite modem is in idle state. */
+    /**
+     * Satellite modem is in idle state.
+     */
     public static final int SATELLITE_MODEM_STATE_IDLE = 0;
-
-    /* Satellite modem is listening for incoming datagrams. */
+    /**
+     * Satellite modem is listening for incoming datagrams.
+     */
     public static final int SATELLITE_MODEM_STATE_LISTENING = 1;
-
-    /* Satellite modem is sending and/or receiving datagrams. */
+    /**
+     * Satellite modem is sending and/or receiving datagrams.
+     */
     public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2;
-
-    /* Satellite modem is powered off. */
-    public static final int SATELLITE_MODEM_STATE_OFF = 3;
+    /**
+     * Satellite modem is retrying to send and/or receive datagrams.
+     */
+    public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3;
+    /**
+     * Satellite modem is powered off.
+     */
+    public static final int SATELLITE_MODEM_STATE_OFF = 4;
+    /**
+     * Satellite modem state is unknown. This generic modem state should be used only when the
+     * modem state cannot be mapped to other specific modem states.
+     */
+    public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1;
 
     /** @hide */
-    @IntDef(prefix = {"SATELLITE_STATE_"},
-            value = {
-                    SATELLITE_MODEM_STATE_IDLE,
-                    SATELLITE_MODEM_STATE_LISTENING,
-                    SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING,
-                    SATELLITE_MODEM_STATE_OFF
-            })
+    @IntDef(prefix = {"SATELLITE_MODEM_STATE_"}, value = {
+            SATELLITE_MODEM_STATE_IDLE,
+            SATELLITE_MODEM_STATE_LISTENING,
+            SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING,
+            SATELLITE_MODEM_STATE_DATAGRAM_RETRYING,
+            SATELLITE_MODEM_STATE_OFF,
+            SATELLITE_MODEM_STATE_UNKNOWN
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteModemState {}
 
-    /** Datagram type indicating that the datagram to be sent or received is of type SOS message. */
+    /**
+     * Datagram type indicating that the datagram to be sent or received is of type SOS message.
+     */
     public static final int DATAGRAM_TYPE_SOS_MESSAGE = 0;
+    /**
+     * Datagram type indicating that the datagram to be sent or received is of type
+     * location sharing.
+     */
+    public static final int DATAGRAM_TYPE_LOCATION_SHARING = 1;
+    /**
+     * Datagram type is unknown. This generic datagram type should be used only when the
+     * datagram type cannot be mapped to other specific datagram types.
+     */
+    public static final int DATAGRAM_TYPE_UNKNOWN = -1;
 
-    /** Datagram type indicating that the datagram to be sent or received is of type
-     * location sharing. */
-    public static final int DATAGRAM_TYPE_LOCATION_SHARING = 3;
-
-    @IntDef(
-            prefix = "DATAGRAM_TYPE_",
-            value = {
-                    DATAGRAM_TYPE_SOS_MESSAGE,
-                    DATAGRAM_TYPE_LOCATION_SHARING,
-            })
+    @IntDef(prefix = "DATAGRAM_TYPE_", value = {
+            DATAGRAM_TYPE_SOS_MESSAGE,
+            DATAGRAM_TYPE_LOCATION_SHARING,
+            DATAGRAM_TYPE_UNKNOWN
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DatagramType {}
 
@@ -802,7 +963,7 @@
     }
 
     /**
-     * Register for the satellite provision state change.
+     * Registers for the satellite provision state changed.
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle the satellite provision state changed event.
@@ -836,7 +997,7 @@
     }
 
     /**
-     * Unregister for the satellite provision state change.
+     * Unregisters for the satellite provision state changed.
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
@@ -918,10 +1079,10 @@
     }
 
     /**
-     * Register for listening to satellite modem state changes.
+     * Registers for modem state changed from satellite modem.
      *
      * @param executor The executor on which the callback will be called.
-     * @param callback The callback to handle the satellite modem state change event.
+     * @param callback The callback to handle the satellite modem state changed event.
      *
      * @return The {@link SatelliteError} result of the operation.
      *
@@ -929,7 +1090,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @SatelliteError public int registerForSatelliteModemStateChange(
+    @SatelliteError public int registerForSatelliteModemStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(executor);
@@ -939,41 +1100,41 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 callback.setExecutor(executor);
-                return telephony.registerForSatelliteModemStateChange(mSubId,
+                return telephony.registerForSatelliteModemStateChanged(mSubId,
                         callback.getBinder());
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("registerForSatelliteModemStateChange() RemoteException:" + ex);
+            loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
             ex.rethrowFromSystemServer();
         }
         return SATELLITE_REQUEST_FAILED;
     }
 
     /**
-     * Unregister to stop listening to satellite modem state changes.
+     * Unregisters for modem state changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteModemStateChange(Executor, SatelliteStateCallback)}.
+     * {@link #registerForSatelliteModemStateChanged(Executor, SatelliteStateCallback)}.
      *
      * @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 unregisterForSatelliteModemStateChange(@NonNull SatelliteStateCallback callback) {
+    public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteModemStateChange(mSubId, callback.getBinder());
+                telephony.unregisterForSatelliteModemStateChanged(mSubId, callback.getBinder());
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("unregisterForSatelliteModemStateChange() RemoteException:" + ex);
+            loge("unregisterForSatelliteModemStateChanged() RemoteException:" + ex);
             ex.rethrowFromSystemServer();
         }
     }
diff --git a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
index cda7a75..e3e4171 100644
--- a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
@@ -39,24 +39,24 @@
         }
 
         @Override
-        public void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo) {
+        public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 mExecutor.execute(() ->
-                        mLocalCallback.onSatellitePositionUpdate(pointingInfo));
+                        mLocalCallback.onSatellitePositionChanged(pointingInfo));
             } finally {
                 restoreCallingIdentity(callingIdentity);
             }
         }
 
         @Override
-        public void onDatagramTransferStateUpdate(
+        public void onDatagramTransferStateChanged(
                 @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
                 int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 mExecutor.execute(() ->
-                        mLocalCallback.onDatagramTransferStateUpdate(
+                        mLocalCallback.onDatagramTransferStateChanged(
                                 state, sendPendingCount, receivePendingCount, errorCode));
             } finally {
                 restoreCallingIdentity(callingIdentity);
@@ -69,23 +69,23 @@
     }
 
     /**
-     * Called when the satellite position changes.
+     * Called when the satellite position changed.
      *
      * @param pointingInfo The pointing info containing the satellite location.
      */
-    public void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo) {
+    public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
         // Base Implementation
     }
 
     /**
-     * Called when satellite datagram transfer state changes.
+     * 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 onDatagramTransferStateUpdate(
+    public void onDatagramTransferStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
             int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
         // Base Implementation
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 81701a2..d24bee6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -38,11 +38,11 @@
         }
 
         @Override
-        public void onSatelliteModemStateChange(@SatelliteManager.SatelliteModemState int state) {
+        public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 mExecutor.execute(() ->
-                        mLocalCallback.onSatelliteModemStateChange(state));
+                        mLocalCallback.onSatelliteModemStateChanged(state));
             } finally {
                 restoreCallingIdentity(callingIdentity);
             }
@@ -68,7 +68,7 @@
      * Called when satellite modem state changes.
      * @param state The new satellite modem state.
      */
-    public void onSatelliteModemStateChange(@SatelliteManager.SatelliteModemState int state) {
+    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
         // Base Implementation
     }
 
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index e0c31ed..5dc1a65 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -46,10 +46,11 @@
     void setSatelliteListener(in ISatelliteListener listener, in IIntegerConsumer errorCallback);
 
     /**
-     * Enable or disable the satellite service listening mode.
+     * Request to enable or disable the satellite service listening mode.
      * 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 errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -62,7 +63,8 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void setSatelliteListeningEnabled(in boolean enable, in IIntegerConsumer errorCallback);
+    void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode,
+            in IIntegerConsumer errorCallback);
 
     /**
      * Request to enable or disable the satellite modem. If the satellite modem is enabled,
@@ -149,7 +151,7 @@
     /**
      * User started pointing to the satellite.
      * The satellite service should report the satellite pointing info via
-     * ISatelliteListener#onSatellitePointingInfoChanged as the user device/satellite moves.
+     * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
      *
      * @param errorCallback The callback to receive the error code result of the operation.
      *
@@ -275,7 +277,7 @@
     /**
      * Poll the pending datagrams to be received over satellite.
      * The satellite service should check if there are any pending datagrams to be received over
-     * satellitea and report them via ISatelliteListener#onNewDatagrams.
+     * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
      *
      * @param errorCallback The callback to receive the error code result of the operation.
      *
@@ -298,10 +300,9 @@
 
     /**
      * Send datagram over satellite.
-     * Once sent, the satellite service should report whether the operation was successful via
-     * SatelliteListener#onDatagramsDelivered.
      *
      * @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.
      *
@@ -321,8 +322,8 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency,
-            in IIntegerConsumer errorCallback);
+    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isDemoMode,
+            in boolean isEmergency, in IIntegerConsumer errorCallback);
 
     /**
      * Request the current satellite modem state.
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index 5ecd442..e24e892e 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -19,6 +19,7 @@
 import android.telephony.satellite.stub.NTRadioTechnology;
 import android.telephony.satellite.stub.PointingInfo;
 import android.telephony.satellite.stub.SatelliteDatagram;
+import android.telephony.satellite.stub.SatelliteError;
 import android.telephony.satellite.stub.SatelliteModemState;
 
 /**
@@ -35,10 +36,10 @@
     /**
      * Indicates that new datagrams have been received on the device.
      *
-     * @param datagrams New datagrams received.
+     * @param datagrams Array of new datagrams received.
      * @param pendingCount The number of datagrams that are pending.
      */
-    void onNewDatagrams(in SatelliteDatagram[] datagrams, in int pendingCount);
+    void onSatelliteDatagramsReceived(in SatelliteDatagram[] datagrams, in int pendingCount);
 
     /**
      * Indicates that the satellite has pending datagrams for the device to be pulled.
@@ -52,27 +53,19 @@
      *
      * @param pointingInfo The current pointing info.
      */
-    void onSatellitePointingInfoChanged(in PointingInfo pointingInfo);
+    void onSatellitePositionChanged(in PointingInfo pointingInfo);
 
     /**
      * Indicates that the satellite modem state has changed.
      *
-     * @param mode The current satellite modem state.
+     * @param state The current satellite modem state.
      */
-    void onSatelliteModemStateChanged(in SatelliteModemState mode);
+    void onSatelliteModemStateChanged(in SatelliteModemState state);
 
     /**
      * Indicates that the satellite radio technology has changed.
      *
-     * @param technology The current satellite service mode.
+     * @param technology The current satellite radio technology.
      */
     void onSatelliteRadioTechnologyChanged(in NTRadioTechnology technology);
-
-    /**
-     * Indicates that datagram transfer is complete and all datagrams have been delivered.
-     *
-     * @param delivered True means all datagrams have been delivered and false means there was an
-     *                  error in delivering all datagrams.
-     */
-    void onDatagramsDelivered(in boolean delivered);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/NTRadioTechnology.aidl b/telephony/java/android/telephony/satellite/stub/NTRadioTechnology.aidl
index 64cfa85..3316408 100644
--- a/telephony/java/android/telephony/satellite/stub/NTRadioTechnology.aidl
+++ b/telephony/java/android/telephony/satellite/stub/NTRadioTechnology.aidl
@@ -21,12 +21,25 @@
  */
 @Backing(type="int")
 enum NTRadioTechnology {
-    /* 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. */
-    NB_IOT_NTN,
-    /* 3GPP 5G NR over Non-Terrestrial-Networks technology. */
-    NR_NTN,
-    /* 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology. */
-    EMTC_NTN,
-    /* Proprietary technology. */
-    PROPRIETARY,
+    /**
+     * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
+     */
+    NB_IOT_NTN = 0,
+    /*
+     * 3GPP 5G NR over Non-Terrestrial-Networks technology.
+     */
+    NR_NTN = 1,
+    /**
+     * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology.
+     */
+    EMTC_NTN = 2,
+    /**
+     * Proprietary technology.
+     */
+    PROPRIETARY = 3,
+    /**
+     * Unknown Non-Terrestrial radio technology. This generic radio technology should be used
+     * only when the radio technology cannot be mapped to other specific radio technologies.
+     */
+    UNKNOWN = -1,
 }
diff --git a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
index b515b3e..83392dd 100644
--- a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
+++ b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
@@ -30,18 +30,20 @@
      */
     float satelliteElevation;
 
-    /** Antenna azimuth in degrees */
-    float mAntennaAzimuthDegrees;
+    /**
+     * 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 mAntennaPitchDegrees;
+    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 mAntennaRollDegrees;
+    float antennaRoll;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 4140939..df51432 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -16,7 +16,6 @@
 
 package android.telephony.satellite.stub;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -26,8 +25,6 @@
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.util.TelephonyUtils;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
@@ -42,53 +39,6 @@
 public class SatelliteImplBase extends SatelliteService {
     private static final String TAG = "SatelliteImplBase";
 
-    /** @hide */
-    @IntDef(prefix = "NT_RADIO_TECHNOLOGY_", value = {
-            NT_RADIO_TECHNOLOGY_NB_IOT_NTN,
-            NT_RADIO_TECHNOLOGY_NR_NTN,
-            NT_RADIO_TECHNOLOGY_EMTC_NTN,
-            NT_RADIO_TECHNOLOGY_PROPRIETARY
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface NTRadioTechnology {}
-
-    /** 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. */
-    public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN =
-            android.telephony.satellite.stub.NTRadioTechnology.NB_IOT_NTN;
-    /** 3GPP 5G NR over Non-Terrestrial-Networks technology. */
-    public static final int NT_RADIO_TECHNOLOGY_NR_NTN =
-            android.telephony.satellite.stub.NTRadioTechnology.NR_NTN;
-    /** 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology. */
-    public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN =
-            android.telephony.satellite.stub.NTRadioTechnology.EMTC_NTN;
-    /** Proprietary technology. */
-    public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY =
-            android.telephony.satellite.stub.NTRadioTechnology.PROPRIETARY;
-
-    /** @hide */
-    @IntDef(prefix = "SATELLITE_MODEM_STATE_", value = {
-            SATELLITE_MODEM_STATE_IDLE,
-            SATELLITE_MODEM_STATE_LISTENING,
-            SATELLITE_MODEM_STATE_MESSAGE_TRANSFERRING,
-            SATELLITE_MODEM_STATE_OFF
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface SatelliteModemState {}
-
-    /** Satellite modem is in idle state. */
-    public static final int SATELLITE_MODEM_STATE_IDLE =
-            android.telephony.satellite.stub.SatelliteModemState.SATELLITE_MODEM_STATE_IDLE;
-    /** Satellite modem is listening for incoming messages. */
-    public static final int SATELLITE_MODEM_STATE_LISTENING =
-            android.telephony.satellite.stub.SatelliteModemState.SATELLITE_MODEM_STATE_LISTENING;
-    /** Satellite modem is sending and/or receiving messages. */
-    public static final int SATELLITE_MODEM_STATE_MESSAGE_TRANSFERRING =
-            android.telephony.satellite.stub.SatelliteModemState
-                    .SATELLITE_MODEM_STATE_MESSAGE_TRANSFERRING;
-    /** Satellite modem is powered off. */
-    public static final int SATELLITE_MODEM_STATE_OFF =
-            android.telephony.satellite.stub.SatelliteModemState.SATELLITE_MODEM_STATE_OFF;
-
     protected final Executor mExecutor;
 
     /**
@@ -121,12 +71,12 @@
         }
 
         @Override
-        public void setSatelliteListeningEnabled(boolean enable, IIntegerConsumer errorCallback)
-                throws RemoteException {
+        public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .setSatelliteListeningEnabled(enable, errorCallback),
-                    "setSatelliteListeningEnabled");
+                            .requestSatelliteListeningEnabled(enable, isDemoMode, errorCallback),
+                    "requestSatelliteListeningEnabled");
         }
 
         @Override
@@ -223,11 +173,12 @@
         }
 
         @Override
-        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency,
-                IIntegerConsumer errorCallback) throws RemoteException {
+        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isDemoMode,
+                boolean isEmergency, IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .sendSatelliteDatagram(datagram, isEmergency, errorCallback),
+                            .sendSatelliteDatagram(
+                                    datagram, isDemoMode, isEmergency, errorCallback),
                     "sendSatelliteDatagram");
         }
 
@@ -296,10 +247,11 @@
     }
 
     /**
-     * Enable or disable the satellite service listening mode.
+     * Request to enable or disable the satellite service listening mode.
      * 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 errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -312,7 +264,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void setSatelliteListeningEnabled(boolean enable,
+    public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
@@ -411,7 +363,7 @@
     /**
      * User started pointing to the satellite.
      * The satellite service should report the satellite pointing info via
-     * ISatelliteListener#onSatellitePointingInfoChanged as the user device/satellite moves.
+     * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
      *
      * @param errorCallback The callback to receive the error code result of the operation.
      *
@@ -549,8 +501,9 @@
     }
 
     /**
-     * Poll the pending datagrams.
-     * The satellite service should report the new datagrams via ISatelliteListener#onNewDatagrams.
+     * Poll the pending datagrams to be received over satellite.
+     * 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.
      *
@@ -575,10 +528,9 @@
 
     /**
      * Send datagram over satellite.
-     * Once sent, the satellite service should report whether the operation was successful via
-     * SatelliteListener#onDatagramsDelivered.
      *
      * @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.
      *
@@ -598,8 +550,8 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
-            @NonNull IIntegerConsumer errorCallback) {
+    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isDemoMode,
+            boolean isEmergency, @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
index 3f5ee56..5ee7f9a 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
@@ -21,12 +21,29 @@
  */
 @Backing(type="int")
 enum SatelliteModemState {
-    /* Satellite modem is in idle state. */
+    /**
+     * Satellite modem is in idle state.
+     */
     SATELLITE_MODEM_STATE_IDLE = 0,
-    /* Satellite modem is listening for incoming messages. */
+    /**
+     * Satellite modem is listening for incoming datagrams.
+     */
     SATELLITE_MODEM_STATE_LISTENING = 1,
-    /* Satellite modem is sending and/or receiving messages. */
-    SATELLITE_MODEM_STATE_MESSAGE_TRANSFERRING = 2,
-    /* Satellite modem is powered off. */
-    SATELLITE_MODEM_STATE_OFF = 3,
+    /**
+     * Satellite modem is sending and/or receiving datagrams.
+     */
+    SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2,
+    /**
+     * Satellite modem is retrying to send and/or receive datagrams.
+     */
+    SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3,
+    /**
+     * Satellite modem is powered off.
+     */
+    SATELLITE_MODEM_STATE_OFF = 4,
+    /**
+     * Satellite modem state is unknown. This generic modem state should be used only when the
+     * modem state cannot be mapped to other specific modem states.
+     */
+    SATELLITE_MODEM_STATE_UNKNOWN = -1,
 }
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index af4edc4..632a687 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -236,8 +236,6 @@
 
     int getSlotIndex(int subId);
 
-    int[] getSubIds(int slotIndex);
-
     int getSubId(int slotIndex);
 
     int getDefaultSubId();
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5bf55ef..f4801c2 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2728,6 +2728,29 @@
     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
+     *              enabled for.
+     * @param receiver Result receiver to get the error code of the request and whether the
+     *                 satellite demo mode is enabled.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestIsSatelliteDemoModeEnabled(int subId, in ResultReceiver receiver);
+
+    /**
      * Request to get whether the satellite service is supported on the device.
      *
      * @param subId The subId of the subscription to check whether satellite is supported for.
@@ -2815,9 +2838,9 @@
 
 
     /**
-     * Register for the satellite provision state change.
+     * Registers for provision state changed from satellite modem.
      *
-     * @param subId The subId of the subscription to register for provision state changes.
+     * @param subId The subId of the subscription to register for provision state changed.
      * @param callback The callback to handle the satellite provision state changed event.
      *
      * @return The {@link SatelliteError} result of the operation.
@@ -2828,10 +2851,10 @@
             in ISatelliteProvisionStateCallback callback);
 
     /**
-     * Unregister for the satellite provision state change.
+     * Unregisters for provision state changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for provision state changes.
+     * @param subId The subId of the subscription to unregister for provision state changed.
      * @param callback The callback that was passed to registerForSatelliteProvisionStateChanged.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
@@ -2851,27 +2874,27 @@
     void requestIsSatelliteProvisioned(int subId, in ResultReceiver receiver);
 
     /**
-     * Register for listening to satellite modem state changes.
+     * Registers for modem state changed from satellite modem.
      *
-     * @param subId The subId of the subscription to register for satellite modem state changes.
+     * @param subId The subId of the subscription to register for satellite modem state changed.
      * @param callback The callback to handle the satellite modem state changed event.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteModemStateChange(int subId, ISatelliteStateCallback callback);
+    int registerForSatelliteModemStateChanged(int subId, ISatelliteStateCallback callback);
 
     /**
-     * Unregister to stop listening to satellite modem state changes.
+     * Unregisters for modem state changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for satellite modem state changes.
-     * @param callback The callback that was passed to registerForSatelliteStateChange.
+     * @param subId The subId of the subscription to unregister for satellite modem state changed.
+     * @param callback The callback that was passed to registerForSatelliteStateChanged.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForSatelliteModemStateChange(int subId, ISatelliteStateCallback callback);
+    void unregisterForSatelliteModemStateChanged(int subId, ISatelliteStateCallback callback);
 
    /**
      * Register to receive incoming datagrams over satellite.
diff --git a/tests/SilkFX/assets/gainmaps/granddam.jpg b/tests/SilkFX/assets/gainmaps/granddam.jpg
new file mode 100644
index 0000000..823f14e
--- /dev/null
+++ b/tests/SilkFX/assets/gainmaps/granddam.jpg
Binary files differ
diff --git a/tests/SilkFX/assets/gainmaps/lightbulb.jpg b/tests/SilkFX/assets/gainmaps/lightbulb.jpg
new file mode 100644
index 0000000..232c5f0
--- /dev/null
+++ b/tests/SilkFX/assets/gainmaps/lightbulb.jpg
Binary files differ
diff --git a/tests/SilkFX/assets/gainmaps/porsche911.jpg b/tests/SilkFX/assets/gainmaps/porsche911.jpg
new file mode 100644
index 0000000..50f4fc8
--- /dev/null
+++ b/tests/SilkFX/assets/gainmaps/porsche911.jpg
Binary files differ